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

时间:2010-04-14 18:54:18

标签: clojure

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

(. Integer parseInt  numberString)

是否有更多的clojuriffic方式可以处理整数和浮点数,并返回clojure数字?我并不特别担心这里的性能,我只是想在文件中处理一堆空白分隔的数字,并以最简单的方式对它们做些什么。

因此文件可能包含以下行:

5  10  0.0002
4  12  0.003

我希望能够将线条转换为数字向量。

10 个答案:

答案 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.matchNumberclojure.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)

使用bigintbigdec

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