web-dev-qa-db-ja.com

Clojureで数値を解析する最も簡単な方法は何ですか?

私はJavaを使用して数値を解析しています。

(. Integer parseInt  numberString)

整数と浮動小数点の両方を処理し、clojureの数値を返す、よりclojurifficな方法はありますか?ここでのパフォーマンスは特に心配していません。ファイル内の空白で区切られた多数の数字を処理し、可能な限り最も簡単な方法でそれらを処理したいだけです。

そのため、ファイルには次のような行が含まれる場合があります。

5  10  0.0002
4  12  0.003

そして、線を数字のベクトルに変換できるようにしたいと思います。

53
Rob Lachlan

edn リーダーを使用して、数値を解析できます。これには、必要に応じてフロートまたはBignumを提供するという利点もあります。

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)

または、1行に1つのベクトル:

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

私は他の方法があると確信しています。

61
Brian Carper

これが「最も簡単な方法」であるかどうかはわかりませんが、それはちょっと楽しいと思ったので...

(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のプライベート静的メソッドであり、いつでも変更または削除できます。

25
Michał Marczyk

より安全にしたい場合は、Float/parseFloatを使用できます

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

私の意見では、あなたが任意の番号に対してそれを望むときに機能し、それが数字ではないときに機能する最良/最も安全な方法はこれです:

(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

Int、floats/doubles、bignumsなどで機能します。他の表記の読み取りのサポートを追加する場合は、単に正規表現を拡張します。

19
solussd

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」を8進数として解析しようとするためであり、090は有効な8進数ではありません。

15
Stathis Sideris

ブライアン・カーパーの答えはほぼ正しい。 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

シンプル、簡単、実行安全;)

14
carocad

bigintbigdecを使用します

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

Clojureのbigint可能な場合はプリミティブを使用します 。正規表現を避けながら、8進リテラルまたは他の数値型のサイズの制限の問題により、(Integer. "10000000000")が失敗します。

(この最後の出来事は私に起こり、それは非常に混乱していた:私はそれをparse-int関数にラップし、その後parse-intは「32bit整数の解析」ではなく「自然整数の解析」を意味すると仮定した)

7
berdario

私は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
Kevin Zhu

これらは、2つの最良かつ正しいアプローチです。

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

これにより、ユースケースにとって重要な場合に、数値を解析するタイプを正確に制御できます。

Clojure EDNリーダーの使用:

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

信頼できない入力では安全ではないread-stringclojure.coreを使用するのとは異なり、edn/read-stringはユーザー入力などの信頼できない入力で安全に実行できます。

これは、多くの場合、Java interopよりも便利です。型を特定に制御する必要がない場合。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

0
Didier A.