【问题标题】:What's the easiest way to parse numbers in clojure?在clojure中解析数字的最简单方法是什么?
【发布时间】:2011-02-08 01:41:10
【问题描述】:

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

(. Integer parseInt  numberString)

是否有更 clojuriffic 方式可以同时处理整数和浮点数并返回 clojure 数字?我并不特别担心这里的性能,我只是想以最直接的方式处理文件中的一堆以空格分隔的数字并对其进行处理。

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

5  10  0.0002
4  12  0.003

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

【问题讨论】:

  • 附带说明,您在帖子中调用 Java 的方式是一种不加糖的方式。调用java时请优先选择加糖的方式。 (Integer/parseInt number-string),例如方法:(.method obj args)
  • 除了 Rayne 的“甜蜜”建议之外,您还可以使用 (Integer. number-string) 将 a 解析为 java.lang.Integer(对于 Long、Double 等类似)。
  • 似乎标题中的“最简单”一词吸引了一些简单且不安全的答案。请,除非您想被黑客入侵,否则请使用可以容忍恶意字符串的数字解析器。

标签: clojure


【解决方案1】:
(def mystring "5")
(Float/parseFloat mystring)

【讨论】:

  • 请编辑您的答案并通过解释您的答案如何解决相关问题来添加一些上下文,而不是发布仅代码的答案。
【解决方案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")

完整列表在这里:https://www.rubberducking.com/2019/05/clojure-for-non-clojure-programmers.html#numbers

【讨论】:

  • 这应该是公认的答案。比最佳答案更有用,提倡更少的不良做法。
【解决方案3】:

您可以使用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]

虽然有点老套。或者有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])

我相信还有其他方法。

【讨论】:

  • 此方法还具有正确解析有理数的有用好处。如果你正确修改了 re-seq (re-seq #"[\d\/\.]+" 输入)
  • 在现代,我们拥有 EDN 阅读器,这将是完成这项工作的合适工具。
  • 这是一种可怕的做法。 read-string 可以执行代码。这个事实怎么强调都不为过。即使*read-eval* 绑定为假,有关示例和对它有多糟糕的很好解释,请参阅:clojuredocs.org/clojure.core/read
  • 还有clojure.edn/read-string只解析edn格式,不会执行代码。
  • 另一个“用户要小心”,与许多编程语言一样,带有前导零的数字被 clojure 阅读器视为八进制。
【解决方案4】:

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

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

【讨论】:

    【解决方案5】:

    使用bigintbigdec

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

    Clojure 的bigintwill use primitives when possible,同时避免了正则表达式、八进制文字的问题或其他数字类型的大小有限,导致(Integer. "10000000000") 失败。

    (这最后一件事发生在我身上,非常令人困惑:我将它包装到 parse-int 函数中,然后假设 parse-int 的意思是“解析一个自然整数”而不是“解析一个 32 位整数”)

    【讨论】:

      【解决方案6】:

      我发现 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)))
      

      例如

      (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】:

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

        (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
        

        适用于 ints、floats/doubles、bignums 等。如果您想添加对读取其他符号的支持,只需增加正则表达式即可。

        【讨论】:

          【解决方案8】:

          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 不是有效的八进制!

          【讨论】:

          • 更适合作为对引用答案的评论。
          【解决方案9】:

          不确定这是否是“最简单的方法”,但我认为这很有趣,所以...通过反射黑客,您可以只访问 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 的私有静态方法,可以随时更改或删除。

          【讨论】:

            【解决方案10】:

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

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

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2014-05-17
              • 1970-01-01
              • 2013-07-06
              • 2023-03-17
              • 1970-01-01
              • 1970-01-01
              • 2017-02-11
              相关资源
              最近更新 更多