我一直在使用java来解析数字,例如
(. Integer parseInt numberString)
是否有更多的clojuriffic方式可以处理整数和浮点数,并返回clojure数字?我并不特别担心这里的性能,我只是想在文件中处理一堆空白分隔的数字,并以最简单的方式对它们做些什么。
因此文件可能包含以下行:
5 10 0.0002
4 12 0.003
我希望能够将线条转换为数字向量。
答案 0 :(得分:59)
您可以使用edn阅读器来解析数字。这样做的好处是可以在需要时为您提供浮子或Bignums。
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]
虽然有点hacky。或者有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])
我确信还有其他方法。
答案 1 :(得分:25)
不确定这是否是“最简单的方式”,但我觉得这很有趣,所以...通过反射黑客,你只能访问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
的支票,如果需要,可以自己投票。
至于性能,让我们有一个不科学的微基准标记(两个函数都已经“预热”;初始运行像往常一样慢):
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
的私有静态方法,可以随时更改或删除。
答案 2 :(得分:22)
如果您想更安全,可以使用Float / parseFloat
user=> (map #(Float/parseFloat (% 0)) (re-seq #"\d+(\.\d+)?" "1 2.2 3.5"))
(1.0 2.2 3.5)
user=>
答案 3 :(得分:19)
在我看来,当你想要任何数字时,最好/最安全的方式是有效的,当它不是数字时失败是这样的:
(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。
(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
适用于整数,浮点数/双打,bignums等。如果你想增加对其他符号的支持,只需增加正则表达式。
答案 4 :(得分:15)
Brian Carper建议的方法(使用read-string)可以很好地工作,但只有在您尝试解析零填充数字(如“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不是有效的八进制!
答案 5 :(得分:14)
Brian carper的回答几乎是正确的。而不是直接从clojure的核心使用读取字符串。使用clojure.edn / read-string。它是安全的,它会解析你抛出的任何东西。
(ns edn-example.core
(require [clojure.edn :as edn]))
(edn/read-string "2.7"); float 2.7
(edn/read-string "2"); int 2
简单,容易且执行安全;)
答案 6 :(得分:7)
我发现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。
(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
答案 7 :(得分:7)
使用bigint
和bigdec
(bigint "1")
(bigint "010") ; returns 10N as expected
(bigint "111111111111111111111111111111111111111111111111111")
(bigdec "11111.000000000000000000000000000000000000000000001")
Clojure的bigint
will use primitives when possible,同时避免使用正则表达式,八进制文字的问题或其他数字类型的有限大小,导致(Integer. "10000000000")
失败。
(这最后一件事发生在我身上并且非常令人困惑:我把它包装成parse-int
函数,然后假设parse-int
意味着“解析一个自然整数”而不是“解析一个32位”整数“)
答案 8 :(得分:0)
这是两种最佳且正确的方法:
使用Java互操作:
complianceType: ComplianceType;
complianceType = ComplianceType.ENGINEER_ASSESMENT;
这对您的用例很重要,因此您可以精确控制要解析数字的类型。
使用Clojure EDN阅读器:
(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")
中的read-string
(在不可信输入中使用不安全)不同,clojure.core
在不可信输入(例如用户输入)上运行是安全的。
如果您不需要对类型进行特定控制,则这通常比Java互操作更为方便。它可以解析Clojure可以解析的任何数字文字,例如:
edn/read-string
完整列表在这里:https://www.rubberducking.com/2019/05/clojure-for-non-clojure-programmers.html#numbers
答案 9 :(得分:0)
(def mystring "5")
(Float/parseFloat mystring)