programing

클로저에서 숫자를 구문 분석하는 가장 쉬운 방법은 무엇입니까?

sourcetip 2021. 1. 16. 11:17
반응형

클로저에서 숫자를 구문 분석하는 가장 쉬운 방법은 무엇입니까?


나는 자바를 사용하여 숫자를 구문 분석했습니다.

(. Integer parseInt  numberString)

정수와 부동 소수점을 모두 처리하고 클로저 숫자를 반환하는 클로저 리프적인 방법이 있습니까? 저는 여기서 성능에 대해 특별히 걱정하지 않습니다. 파일에서 공백으로 구분 된 숫자를 처리하고 가능한 가장 간단한 방법으로 작업을 수행하고 싶습니다.

따라서 파일에는 다음과 같은 줄이있을 수 있습니다.

5  10  0.0002
4  12  0.003

그리고 선을 숫자 벡터로 변환 할 수 있기를 원합니다.


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

다른 방법이 있다고 확신합니다.


이것이 "가장 쉬운 방법"인지 확실하지 않지만, 재미 있다고 생각했습니다. 그래서 ... 리플렉션 해킹을 사용하면 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언제든지 변경 또는 제거 할 수 있습니다.


더 안전하고 싶다면 Float / parseFloat를 사용할 수 있습니다.

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

제 생각에 어떤 숫자를 원할 때 작동하고 숫자가 아닐 때 실패하는 가장 안전한 방법은 다음과 같습니다.

(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

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


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!


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


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

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


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

ReferenceURL : https://stackoverflow.com/questions/2640169/whats-the-easiest-way-to-parse-numbers-in-clojure

반응형