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
简单,容易和执行安全;)
使用bigint
和bigdec
(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