What's the easiest way to parse numbers in clojure?

后端 未结 10 1530
长发绾君心
长发绾君心 2020-12-08 09:30

I\'ve been using java to parse numbers, e.g.

(. Integer parseInt  numberString)

Is there a more clojuriffic way that would handle both int

相关标签:
10条回答
  • 2020-12-08 10:00

    I find solussd's answer work great for my code. Based on it, here's an enhancement with support for Scientific notation. Besides, (.trim s) is added so that extra space can be tolerated.

    (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
    
    0 讨论(0)
  • 2020-12-08 10:02
    (def mystring "5")
    (Float/parseFloat mystring)
    
    0 讨论(0)
  • 2020-12-08 10:10

    These are the two best and correct approaches:

    Using Java interop:

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

    This lets you precisely control the type you want to parse the number in, when that matters to your use case.

    Using the Clojure EDN reader:

    (require '[clojure.edn :as edn])
    (edn/read-string "333")
    

    Unlike using read-string from clojure.core which isn't safe to use on untrusted input, edn/read-string is safe to run on untrusted input such as user input.

    This is often more convenient then the Java interop if you don't need to have specific control of the types. It can parse any number literal that Clojure can parse such as:

    ;; Ratios
    (edn/read-string "22/7")
    ;; Hexadecimal
    (edn/read-string "0xff")
    

    Full list here: https://www.rubberducking.com/2019/05/clojure-for-non-clojure-programmers.html#numbers

    0 讨论(0)
  • 2020-12-08 10:11

    Not sure if this is "the easiest way", but I thought it was kind of fun, so... With a reflection hack, you can access just the number-reading part of Clojure's Reader:

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

    Then use like so:

    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
    

    Notice how this does not throw the usual NumberFormatException if something goes wrong; you could add a check for nil and throw it yourself if you want.

    As for performance, let's have an unscientific microbenchmark (both functions have been "warmed up"; initial runs were slower as usual):

    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
    

    The obvious disclaimer: clojure.lang.LispReader.matchNumber is a private static method of clojure.lang.LispReader and may be changed or removed at any time.

    0 讨论(0)
提交回复
热议问题