programacion - ¿Cuál es la forma más fácil de analizar números en clojure?
clojure lenguaje de programacion (8)
El enfoque sugerido de Brian Carper (usando la cadena de lectura) funciona muy bien, pero solo hasta que intentes analizar números sin relleno como "010". Observar:
user=> (read-string "010")
8
user=> (read-string "090")
java.lang.RuntimeException: java.lang.NumberFormatException: Invalid number: 090 (NO_SOURCE_FILE:0)
¡Esto se debe a que clojure intenta analizar "090" como un octal, y 090 no es un octal válido!
He estado usando Java para analizar números, por ejemplo
(. Integer parseInt numberString)
¿Hay una forma más clojuriffica que manejaría tanto enteros como flotantes, y devolver números de clojure? No estoy especialmente preocupado por el rendimiento aquí, solo quiero procesar un grupo de números delimitados por espacios en blanco en un archivo y hacer algo con ellos, de la manera más directa posible.
Entonces un archivo puede tener líneas como:
5 10 0.0002
4 12 0.003
Y me gustaría poder transformar las líneas en vectores de números.
En mi opinión, la mejor / más segura forma que funciona cuando lo desea para cualquier número y falla cuando no es un número es esto:
(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)))
p.ej
(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
Funciona para ints, floats / dobles, bignums, etc. Si desea agregar soporte para leer otras notaciones, simplemente aumente la expresión regular.
Encuentro que la respuesta de Solussd funciona muy bien para mi código. En base a esto, aquí hay una mejora con soporte para la notación científica. Además, se agrega (.trim s) para que se pueda tolerar espacio adicional.
(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)))
p.ej
(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
La respuesta de Brian Carper es casi correcta. En lugar de usar cadena de lectura directamente desde el núcleo de Clojure. Use clojure.edn / read-string. Es seguro y analizará todo lo que le arrojes.
(ns edn-example.core
(require [clojure.edn :as edn]))
(edn/read-string "2.7"); float 2.7
(edn/read-string "2"); int 2
simple, fácil y seguro de ejecución;)
No estoy seguro si esta es "la manera más fácil", pero pensé que era divertido, así que ... Con un truco de reflexión, puede acceder solo a la parte de lectura de números del lector de 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]))))
Entonces use así:
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
Observe cómo esto no arroja la habitual NumberFormatException
si algo sale mal; podría agregar un cheque para nil
y tirarlo usted mismo si lo desea.
En cuanto al rendimiento, tengamos un microbenchmark no científico (ambas funciones han sido "calentadas", las ejecuciones iniciales fueron más lentas de lo habitual):
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
El descargo de responsabilidad obvio: clojure.lang.LispReader.matchNumber
es un método privado estático de clojure.lang.LispReader
y puede modificarse o eliminarse en cualquier momento.
Si está seguro de que su archivo solo contiene números, puede usar el lector Clojure para analizar los números. Esto tiene la ventaja de darte flotadores o Bignum cuando sea necesario, también.
user> (read-string "0.002")
0.0020
Esto no es seguro si está analizando una entrada arbitraria suministrada por el usuario, ya que las macros de lector se pueden usar para ejecutar código arbitrario en tiempo de lectura y eliminar su disco duro, etc.
Si quieres un gran vector de números, puedes hacer trampa y hacer esto:
user> (let [input "5 10 0.002/n4 12 0.003"]
(read-string (str "[" input "]")))
[5 10 0.0020 4 12 0.0030]
Aunque un poco hacky. O hay 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)
O un vector por línea:
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])
Estoy seguro de que hay otras formas.
Si quieres estar más seguro, puedes usar Float / parseFloat
user=> (map #(Float/parseFloat (% 0)) (re-seq #"/d+(/./d+)?" "1 2.2 3.5"))
(1.0 2.2 3.5)
user=>
Usa bigint
y bigdec
(bigint "1")
(bigint "010") ; returns 10N as expected
(bigint "111111111111111111111111111111111111111111111111111")
(bigdec "11111.000000000000000000000000000000000000000000001")
Bigint de Clojure usará primitivas cuando sea posible , mientras evita las expresiones regulares, el problema con los literales octales o el tamaño limitado de los otros tipos numéricos, lo que hace que (Integer. "10000000000")
falle.
(Esto me pasó a mí y fue bastante confuso: lo envolví en una función de parse-int
, y luego simplemente asumí que parse-int
significaba "analizar un entero natural" no "analizar un entero de 32 bits")