在 Clojure 中如何将 String 转换为数字?

我有各种各样的字符串,有些像“45”,有些像“45px”。怎么把这两个转换成数字45?

94574 次浏览

这不是完美的,但这里有一些与 filterCharacter/isDigitInteger/parseInt。对于浮点数不起作用,如果输入中没有数字,它就会失败,所以您可能应该清除它。我希望有一个更好的方法来做到这一点,而不是涉及太多的 Java。

user=> (defn strToInt [x] (Integer/parseInt (apply str (filter #(Character/isDigit %) x))))
#'user/strToInt
user=> (strToInt "45px")
45
user=> (strToInt "45")
45
user=> (strToInt "a")
java.lang.NumberFormatException: For input string: "" (NO_SOURCE_FILE:0)

AFAIK 对于你的问题没有标准的解决方案。我认为下面这些使用 clojure.contrib.str-utils2/replace的东西会有所帮助:

(defn str2int [txt]
(Integer/parseInt (replace txt #"[a-zA-Z]" "")))
(defn parse-int [s]
(Integer. (re-find #"[0-9]*" s)))


user> (parse-int "10px")
10
user> (parse-int "10")
10

新答案

我更喜欢 Snbot 的回答。对于这个简单的用例,使用 Java 方法比使用 read-string 更简单,也更健壮。我确实做了一些小改动。因为作者没有排除负数,所以我把它调整为允许出现负数。我还让它要求数字从字符串的开头开始。

(defn parse-int [s]
(Integer/parseInt (re-find #"\A-?\d+" s)))

另外,我发现当没有给出基数时,Integer/parseInt 解析为小数,即使有前导零也是如此。

旧答案

首先,解析一个整数(因为这是 google 上的热门话题,而且它是很好的背景信息) :

你可以使用 读者:

(read-string "9") ; => 9

你可以看看是不是一个数字:

(defn str->int [str] (if (number? (read-string str))))

我不确定 clojure 阅读器是否可以信任用户输入,这样你就可以在读取之前检查:

(defn str->int [str] (if (re-matches (re-pattern "\\d+") str) (read-string str)))

我想我更喜欢最后一种解决方案。

现在,回到你的具体问题,解析以整数开头的东西,比如 29px:

(read-string (second (re-matches (re-pattern "(\\d+).*") "29px"))) ; => 29

这将工作在 10pxpx10

(defn parse-int [s]
(Integer. (re-find  #"\d+" s )))

它只会解析第一个连续数字

user=> (parse-int "10not123")
10
user=> (parse-int "abc10def11")
10

我可能会在需求中添加一些内容:

  • 必须从一个数字开始
  • 必须容忍空的输入
  • 允许传递任何对象(toString 是标准的)

比如说:

(defn parse-int [v]
(try
(Integer/parseInt (re-find #"^\d+" (.toString v)))
(catch NumberFormatException e 0)))


(parse-int "lkjhasd")
; => 0
(parse-int (java.awt.Color. 4 5 6))
; => 0
(parse-int "a5v")
; => 0
(parse-int "50px")
; => 50

然后可能使这个方法成为一个多方法,允许用户提供的默认值而不是0的额外加分。

继续补充 snRobot 的回答:

(defn string->integer [s]
(when-let [d (re-find #"-?\d+" s)] (Integer. d)))

如果输入中没有数字,则此版本返回 nil,而不是引发异常。

我的问题是,是否可以将名称缩写为“ str-> int”,或者是否应该总是完全指定这样的内容。

还可以使用 (re-seq)函数将返回值扩展为包含输入字符串中存在的所有数字的字符串,其顺序如下:

(defn turn-to-int [ s ] (- > (重新编号“ d”) (应用 str) (整数))

(convert-to-int "10not123") = > 10123

(type *1) = > java.lang.Integer

这对我来说很有效,直截了当得多。

(read-string“123”)

= > 123

对于简单的情况,您可以只使用正则表达式拉出数字的第一个字符串,如上所述。

如果遇到更复杂的情况,可以使用 InstaParse 库:

(ns tst.parse.demo
(:use tupelo.test)
(:require
[clojure.string :as str]
[instaparse.core :as insta]
[tupelo.core :as t] ))
(t/refer-tupelo)


(dotest
(let [abnf-src            "
size-val      = int / int-px
int           = digits          ; ex '123'
int-px        = digits <'px'>   ; ex '123px'
<digits>      = 1*digit         ; 1 or more digits
<digit>       = %x30-39         ; 0-9
"
tx-map        {:int      (fn fn-int [& args]
[:int (Integer/parseInt (str/join args))])
:int-px   (fn fn-int-px [& args]
[:int-px (Integer/parseInt (str/join args))])
:size-val identity
}


parser              (insta/parser abnf-src :input-format :abnf)
instaparse-failure? (fn [arg] (= (class arg) instaparse.gll.Failure))
parse-and-transform (fn [text]
(let [result (insta/transform tx-map
(parser text))]
(if (instaparse-failure? result)
(throw (IllegalArgumentException. (str result)))
result)))  ]
(is= [:int 123]     (parse-and-transform "123"))
(is= [:int-px 123]  (parse-and-transform "123px"))
(throws?            (parse-and-transform "123xyz"))))

该问题询问如何将字符串解析为数字。

(number? 0.5)
;;=> true

因此,也应该对上述小数进行解析。

也许现在还不能完全回答这个问题,但是作为一般用途,我认为你应该严格要求它是否是一个数字(所以“ px”是不允许的) ,并且让来电者通过返回零来处理非数字:

(defn str->number [x]
(when-let [num (re-matches #"-?\d+\.?\d*" x)]
(try
(Float/parseFloat num)
(catch Exception _
nil))))

如果 Floats 对于您的域而不是 Float/parseFloat是有问题的,那么放入 bigdec或其他内容。

对于其他任何想要将一个更普通的字符串文字解析为数字的人,也就是说,一个没有其他非数字字符的字符串。这是两种最好的方法:

使用 Java 互操作:

(Long/parseLong "333")
(Float/parseFloat "333.33")
(Double/parseDouble "333.3333333333332")
(Integer/parseInt "-333")
(Integer/parseUnsignedInt "333")
(BigInteger. "3333333333333333333333333332")
(BigDecimal. "3.3333333333333333333333333332")
(Short/parseShort "400")
(Byte/parseByte "120")

这使您可以精确地控制您想要解析数字的类型,当这与您的用例有关时。

使用 Clojure EDN 阅读器:

(require '[clojure.edn :as edn])
(edn/read-string "333")

与使用来自 clojure.coreread-string不同,edn/read-string在不受信任的输入上使用是不安全的,edn/read-string在不受信任的输入(如用户输入)上运行是安全的。

如果不需要对类型进行特定的控制,这通常比 Java 互操作更方便。它可以解析 Clojure 可以解析的任何数字字面值,比如:

;; Ratios
(edn/read-string "22/7")
;; Hexadecimal
(edn/read-string "0xff")

完整列表在这里: https://www.rubberducking.com/2019/05/clojure-for-non-clojure-programmers.html#numbers

为了避免某些字符串出现异常,这个例子怎么样?

(defn string-to-number [in]
(let [s (strip-whitespace in)      ;; trim
f (re-find #"\d+" s)]        ;; search digit else nil
(if f (Integer/parseInt f) 0)))  ;; if not-nil do cast


(string-to-number "-")
(string-to-number "10")
(string-to-number "px10")
(string-to-number "1200 xr")