私はJavaを使用して数値を解析しています。
(. Integer parseInt numberString)
整数と浮動小数点の両方を処理し、clojureの数値を返す、よりclojurifficな方法はありますか?ここでのパフォーマンスは特に心配していません。ファイル内の空白で区切られた多数の数字を処理し、可能な限り最も簡単な方法でそれらを処理したいだけです。
そのため、ファイルには次のような行が含まれる場合があります。
5 10 0.0002
4 12 0.003
そして、線を数字のベクトルに変換できるようにしたいと思います。
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])
私は他の方法があると確信しています。
これが「最も簡単な方法」であるかどうかはわかりませんが、それはちょっと楽しいと思ったので...
(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
Int、floats/doubles、bignumsなどで機能します。他の表記の読み取りのサポートを追加する場合は、単に正規表現を拡張します。
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進数ではありません。
ブライアン・カーパーの答えはほぼ正しい。 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
シンプル、簡単、実行安全;)
bigint
とbigdec
を使用します
(bigint "1")
(bigint "010") ; returns 10N as expected
(bigint "111111111111111111111111111111111111111111111111111")
(bigdec "11111.000000000000000000000000000000000000000000001")
Clojureのbigint
可能な場合はプリミティブを使用します 。正規表現を避けながら、8進リテラルまたは他の数値型のサイズの制限の問題により、(Integer. "10000000000")
が失敗します。
(この最後の出来事は私に起こり、それは非常に混乱していた:私はそれをparse-int
関数にラップし、その後parse-int
は「32bit整数の解析」ではなく「自然整数の解析」を意味すると仮定した)
私は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
これらは、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-string
のclojure.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