clojure中parsing数字的最简单方法是什么?

我一直在使用javaparsing数字,例如

(. Integer parseInt numberString) 

有更多的clojuriffic方式来处理整数和浮点数,并返回clojure数字? 我并不特别担心在这里的performance,我只是想在一个文件中处理一些空格分隔的数字,并以最直接的方式处理它们。

所以一个文件可能有这样的行:

 5 10 0.0002 4 12 0.003 

我希望能够将这些线条转换成数字的vector。

如果你确信你的文件只包含数字,你可以使用Clojure阅读器来parsing数字。 这也有利于在需要时为您提供花车或Bignums。

 user> (read-string "0.002") 0.0020 

如果parsing任意用户提供的input,这是不安全的,因为读取macros可以用来在读取时执行任意代码并删除硬盘等。

如果你想要一个巨大的数字vector,你可以作弊和做到这一点:

 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) 

或每行一个vector:

 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]) 

我相信还有其他的方法。

不知道这是否是“最简单的方式”,但我认为这样很有趣,所以……反思一下,你可以访问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 ,如果你想要扔它自己。

至于性能,让我们有一个不科学的微观基准(这两个function已经“热身”,初始运行速度一如既往):

 user> (time (dotimes [_ 10000] (parse-number "1234123512435"))) "Elapsed time: 564.58196 msecs" nil user> (time (dotimes [_ 10000] (read-string "1234123512435"))) "Elapsed time: 561.425967 msecs" nil 

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

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

 user=> (map #(Float/parseFloat (% 0)) (re-seq #"\d+(\.\d+)?" "1 2.2 3.5")) (1.0 2.2 3.5) user=> 

在我看来,最好/最安全的方式,当你想要的任何数字和失败,当它不是一个数字是这样的:

 (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))) 

例如

 (parse-number "43") ;=> 43 (parse-number "72.02") ;=> 72.02 (parse-number "009.0008") ;=> 9.008 (parse-number "-92837482734982347.00789") ;=> -9.2837482734982352E16 (parse-number "89blah") ;=> nil (parse-number "z29") ;=> nil (parse-number "(exploit-me)") ;=> nil 

适用于整数,浮点/双精度,双精度等。如果您想增加对读取其他符号的支持,只需增加正则expression式即可。

Brian Carperbuild议的方法(使用读取string)很好地工作,但直到您尝试parsing零填充数字(如“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”parsing为八进制数,而090不是有效的八进制数!

Brian carper的答案几乎是正确的。 而不是直接从clojure的核心使用读取string。 使用clojure.edn / read-string。 这是安全的,它会parsing任何你扔在它。

 (ns edn-example.core (require [clojure.edn :as edn])) (edn/read-string "2.7"); float 2.7 (edn/read-string "2"); int 2 

简单,容易和执行安全;)

使用bigintbigdec

 (bigint "1") (bigint "010") ; returns 10N as expected (bigint "111111111111111111111111111111111111111111111111111") (bigdec "11111.000000000000000000000000000000000000000000001") 

Clojure的bigint 将尽可能使用原语 ,同时避免使用正则expression式,八进制文字问题或其他数字types的有限大小,导致(Integer. "10000000000")失败。

(这最后一件事情发生在我身上,这是令人困惑的:我把它包装成一个parse-int函数,之后只是假设parse-int意思是“parsing一个自然整数”而不是“parsing一个32位整数”)

我发现solussd的答案​​对我的代码非常有用。 在此基础上,这是一个支持科学记数法的增强。 此外,(.trim)被添加,以便额外的空间是可以容忍的。

 (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))) 

例如

 (parse-number " 4.841192E-002 ") ;=> 0.04841192 (parse-number " 4.841192e2 ") ;=> 484.1192 (parse-number " 4.841192E+003 ") ;=> 4841.192 (parse-number " 4.841192e.2 ") ;=> nil (parse-number " 4.841192E ") ;=> nil