什么';在clojure中解析数字最简单的方法是什么?

什么';在clojure中解析数字最简单的方法是什么?,clojure,Clojure,我一直在使用java解析数字,例如 (. Integer parseInt numberString) 是否有一种更为复杂的方法来处理整数和浮点数,并返回clojure数?我并不特别担心这里的性能,我只想处理文件中的一组以空格分隔的数字,并以最简单的方式处理它们 因此,文件可能有如下行: 5 10 0.0002 4 12 0.003 我希望能够将行转换为数字向量。您可以使用读取器解析数字。这样做的好处是,在需要时也可以为您提供浮动或bignum user> (require

我一直在使用java解析数字,例如

(. Integer parseInt  numberString)
是否有一种更为复杂的方法来处理整数和浮点数,并返回clojure数?我并不特别担心这里的性能,我只想处理文件中的一组以空格分隔的数字,并以最简单的方式处理它们

因此,文件可能有如下行:

5  10  0.0002
4  12  0.003
我希望能够将行转换为数字向量。

您可以使用读取器解析数字。这样做的好处是,在需要时也可以为您提供浮动或bignum

user> (require '[clojure.edn :as edn])
nil
user> (edn/read-string "0.002")
0.0020
如果你想要一个巨大的数字向量,你可以作弊并这样做:

user> (let [input "5  10  0.002\n4  12  0.003"]
        (read-string (str "[" input "]")))
[5 10 0.0020 4 12 0.0030]
不过有点老套。或者有
re-seq

user> (let [input "5  10  0.002\n4  12  0.003"]
        (map read-string (re-seq #"[\d.]+" input)))
(5 10 0.0020 4 12 0.0030)
或每行一个向量:

user> (let [input "5  10  0.002\n4  12  0.003"]
        (for [line (line-seq (java.io.BufferedReader.
                              (java.io.StringReader. input)))]
             (vec (map read-string (re-seq #"[\d.]+" line)))))
([5 10 0.0020] [4 12 0.0030])

我相信还有其他方法。

如果您想更安全,可以使用Float/parseFloat

user=> (map #(Float/parseFloat (% 0)) (re-seq #"\d+(\.\d+)?" "1 2.2 3.5"))
(1.0 2.2 3.5)
user=> 
不确定这是不是“最简单的方法”,但我觉得这很有趣,所以。。。使用反射黑客,您可以访问Clojure阅读器的数字读取部分:

(let [m (.getDeclaredMethod clojure.lang.LispReader
                            "matchNumber"
                            (into-array [String]))]
  (.setAccessible m true)
  (defn parse-number [s]
    (.invoke m clojure.lang.LispReader (into-array [s]))))
然后像这样使用:

user> (parse-number "123")
123
user> (parse-number "123.5")
123.5
user> (parse-number "123/2")
123/2
user> (class (parse-number "123"))
java.lang.Integer
user> (class (parse-number "123.5"))
java.lang.Double
user> (class (parse-number "123/2"))
clojure.lang.Ratio
user> (class (parse-number "123123451451245"))
java.lang.Long
user> (class (parse-number "123123451451245123514236146"))
java.math.BigInteger
user> (parse-number "0x12312345145124")
5120577133367588
user> (parse-number "12312345142as36146") ; note the "as" in the middle
nil
注意,如果出现问题,这不会引发通常的
NumberFormatException
;您可以为
nil
添加一个检查,如果需要,您可以自己抛出它

至于性能,让我们使用一个不科学的微基准(两个功能都已“预热”;初始运行与往常一样慢):


显而易见的免责声明:
clojure.lang.LispReader.matchNumber
clojure.lang.LispReader
的一个私有静态方法,可以随时更改或删除。

Brian Carper建议的方法(使用读取字符串)运行良好,但只有在尝试解析零填充数字(如“010”)之前。注意:

user=> (read-string "010")
8
user=> (read-string "090")
java.lang.RuntimeException: java.lang.NumberFormatException: Invalid number: 090 (NO_SOURCE_FILE:0)

这是因为clojure试图将“090”解析为八进制,而090不是有效的八进制

在我看来,对于任何数字,最好/最安全的方法是这样的,当它不是数字时,最好/最安全的方法是:

(defn parse-number
  "Reads a number from a string. Returns nil if not a number."
  [s]
  (if (re-find #"^-?\d+\.?\d*$" s)
    (read-string s)))
e、 g


适用于int、float/double、bignums等。如果您想添加对读取其他符号的支持,只需扩展正则表达式即可。

我发现solussd的答案对我的代码非常有用。基于此,这里有一个支持科学记数法的增强。此外,还添加了(.trim s),以便可以容纳额外的空间

(defn parse-number
  "Reads a number from a string. Returns nil if not a number."
  [s]
  (if (re-find #"^-?\d+\.?\d*([Ee]\+\d+|[Ee]-\d+|[Ee]\d+)?$" (.trim s))
    (read-string s)))
e、 g


使用
bigint
bigdec

(bigint "1")
(bigint "010") ; returns 10N as expected
(bigint "111111111111111111111111111111111111111111111111111")
(bigdec "11111.000000000000000000000000000000000000000000001")
Clojure的
bigint
,在避免regexp的同时,八进制文字或其他数值类型大小有限的问题,导致
(Integer.“1000000000”)
失败


(最后一件事发生在我身上,让我很困惑:我把它包装成了一个
parse int
函数,然后就假设
parse int
的意思是“解析一个自然整数”而不是“解析一个32位整数”)

(ns edn-example.core
    (require [clojure.edn :as edn]))

(edn/read-string "2.7"); float 2.7
(edn/read-string "2"); int 2

简单、容易且执行安全;)

以下是两种最佳且正确的方法:

使用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.core
中的
read string
在不受信任的输入上使用不安全不同,
edn/read string
在不受信任的输入(如用户输入)上运行是安全的

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

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

这里有一个完整的列表:

作为旁注,您在文章中称Java的方式是一种不加修饰的方式。在调用java时,请选择糖化方式
(Integer/parseInt number string)
,例如方法:
(.method obj args)
。除了Rayne的“sweet”建议之外,您还可以使用
(Integer.number string)
将a解析为java.lang.Integer(类似于Long、Double等)。看起来“最简单”这个词在标题中,吸引了一些简单而不安全的答案。请,除非你想被黑客攻击,否则请使用一个可以容忍恶意字符串的数字解析器。这种方法还有正确解析有理数的好处。如果你在现代正确地修改re-seq(re-seq“[\d\/\.]+”输入),我们有EDN阅读器,它将是这项工作的正确工具。这是一种可怕的做法<代码>读取字符串可以执行代码。这一事实再怎么强调也不为过。有关示例和它的糟糕程度的良好解释,即使
*read eval*
绑定为false,请参阅:还有一个只解析edn格式,不执行代码的。这只是另一个“用户注意”而已,clojure阅读器将前导零的数字视为八进制,与许多编程语言一样。更适合作为参考答案的注释。请编辑您的答案,并通过解释您的答案如何解决问题来添加一些上下文,而不是只发布代码答案。这应该是可接受的答案。与顶级答案相比,它更有用,倡导更少的不良做法。
(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")
(require '[clojure.edn :as edn])
(edn/read-string "333")
;; Ratios
(edn/read-string "22/7")
;; Hexadecimal
(edn/read-string "0xff")
(def mystring "5")
(Float/parseFloat mystring)