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

后端 未结 10 1529
长发绾君心
长发绾君心 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 09:51

    If you want to be safer, you can use Float/parseFloat

    user=> (map #(Float/parseFloat (% 0)) (re-seq #"\d+(\.\d+)?" "1 2.2 3.5"))
    (1.0 2.2 3.5)
    user=> 
    
    0 讨论(0)
  • 2020-12-08 09:51

    Brian Carper's suggested approach (using read-string) works nicely, but only until you try and parse zero-padded numbers like "010". Observe:

    user=> (read-string "010")
    8
    user=> (read-string "090")
    java.lang.RuntimeException: java.lang.NumberFormatException: Invalid number: 090 (NO_SOURCE_FILE:0)
    

    This is because clojure tries to parse "090" as an octal, and 090 is not a valid octal!

    0 讨论(0)
  • 2020-12-08 09:52

    Brian carper's answer is almost correct. Instead of using read-string directly from clojure's core. Use clojure.edn/read-string. It is safe and it will parse anything that you throw at it.

    (ns edn-example.core
        (require [clojure.edn :as edn]))
    
    (edn/read-string "2.7"); float 2.7
    (edn/read-string "2"); int 2
    

    simple, easy and execution safe ;)

    0 讨论(0)
  • 2020-12-08 09:54

    You can use the edn reader to parse numbers. This has the benefit of giving you floats or Bignums when needed, too.

    user> (require '[clojure.edn :as edn])
    nil
    user> (edn/read-string "0.002")
    0.0020
    

    If you want one huge vector of numbers, you could cheat and do this:

    user> (let [input "5  10  0.002\n4  12  0.003"]
            (read-string (str "[" input "]")))
    [5 10 0.0020 4 12 0.0030]
    

    Kind of hacky though. Or there's 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)
    

    Or one vector per line:

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

    I'm sure there are other ways.

    0 讨论(0)
  • 2020-12-08 09:57

    Use bigint and bigdec

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

    Clojure's bigint will use primitives when possible, while avoiding regexps, the problem with octal literals or the limited size of the other numeric types, causing (Integer. "10000000000") to fail.

    (This last thing happened to me and it was quite confusing: I wrapped it into a parse-int function, and afterwards just assumed that parse-int meant "parse a natural integer" not "parse a 32bit integer")

    0 讨论(0)
  • 2020-12-08 09:59

    In my opinion the best/safest way that works when you want it to for any number and fails when it isn't a number is this:

    (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
    

    Works for ints, floats/doubles, bignums, etc. If you wanted to add support for reading other notations, simply augment the regex.

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