tutorial programacion lenguaje example ejemplos clojure

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