J'utilise Java pour analyser les nombres, par exemple.
(. Integer parseInt numberString)
Existe-t-il une méthode plus clojuriffique qui traiterait à la fois les nombres entiers et les flottants, et retournerait les nombres de clojure? Je ne suis pas particulièrement préoccupé par les performances ici, je veux juste traiter un tas de nombres délimités par des espaces blancs dans un fichier et faire quelque chose avec eux, de la manière la plus simple possible.
Un fichier peut donc avoir des lignes comme:
5 10 0.0002
4 12 0.003
Et j'aimerais pouvoir transformer les lignes en vecteurs de nombres.
Vous pouvez utiliser le lecteur edn pour analyser les nombres. Cela a l'avantage de vous donner des flotteurs ou des bignums en cas de besoin aussi.
user> (require '[clojure.edn :as edn])
nil
user> (edn/read-string "0.002")
0.0020
Si vous voulez un énorme vecteur de nombres, vous pouvez tricher et faire ceci:
user> (let [input "5 10 0.002\n4 12 0.003"]
(read-string (str "[" input "]")))
[5 10 0.0020 4 12 0.0030]
Un peu hacky cependant. Ou il y a 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)
Ou un vecteur par ligne:
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])
Je suis sûr qu'il existe d'autres moyens.
Je ne sais pas si c'est "le moyen le plus simple", mais je pensais que c'était assez amusant, alors ... Avec un hack de réflexion, vous pouvez accéder uniquement à la partie de lecture des numéros du lecteur 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]))))
Ensuite, utilisez comme ceci:
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
Remarquez comment cela ne lance pas l'habituel NumberFormatException
si quelque chose ne va pas; vous pouvez ajouter un chèque pour nil
et le jeter vous-même si vous le souhaitez.
En ce qui concerne les performances, nous allons avoir un microbenchmark non scientifique (les deux fonctions ont été "réchauffées"; les exécutions initiales ont été plus lentes comme d'habitude):
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
L'avertissement évident: clojure.lang.LispReader.matchNumber
est une méthode statique privée de clojure.lang.LispReader
et peut être modifié ou supprimé à tout moment.
Si vous voulez être plus sûr, vous pouvez utiliser Float/parseFloat
user=> (map #(Float/parseFloat (% 0)) (re-seq #"\d+(\.\d+)?" "1 2.2 3.5"))
(1.0 2.2 3.5)
user=>
À mon avis, le meilleur moyen/le plus sûr qui fonctionne lorsque vous le souhaitez pour n'importe quel numéro et échoue lorsqu'il n'est pas un numéro est le suivant:
(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)))
par exemple.
(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
Fonctionne pour les entiers, les flottants/doubles, les bignums, etc. Si vous souhaitez ajouter un support pour la lecture d'autres notations, augmentez simplement l'expression régulière.
L'approche suggérée par Brian Carper (en utilisant une chaîne de lecture) fonctionne bien, mais seulement jusqu'à ce que vous essayiez d'analyser des nombres remplis de zéro comme "010". Observer:
user=> (read-string "010")
8
user=> (read-string "090")
Java.lang.RuntimeException: Java.lang.NumberFormatException: Invalid number: 090 (NO_SOURCE_FILE:0)
C'est parce que clojure essaie d'analyser "090" comme octal, et 090 n'est pas un octal valide!
La réponse de Brian Carper est presque correcte. Au lieu d'utiliser la chaîne de lecture directement à partir du noyau de clojure. Utilisez clojure.edn/read-string. Il est sûr et analysera tout ce que vous lui lancerez.
(ns edn-example.core
(require [clojure.edn :as edn]))
(edn/read-string "2.7"); float 2.7
(edn/read-string "2"); int 2
simple, facile et sûr d'exécution;)
Utilisez bigint
et bigdec
(bigint "1")
(bigint "010") ; returns 10N as expected
(bigint "111111111111111111111111111111111111111111111111111")
(bigdec "11111.000000000000000000000000000000000000000000001")
bigint
de Clojure utilisera les primitives si possible , tout en évitant les regexps, le problème des littéraux octaux ou la taille limitée des autres types numériques, provoquant (Integer. "10000000000")
échouer.
(Cette dernière chose m'est arrivée et c'était assez déroutant: je l'ai enveloppé dans un parse-int
fonction, et ensuite supposé que parse-int
signifiait "analyser un entier naturel" et non "analyser un entier 32 bits")
Je trouve que la réponse de solussd fonctionne très bien pour mon code. Sur cette base, voici une amélioration prenant en charge la notation scientifique. En outre, (.trim s) est ajouté afin que l'espace supplémentaire puisse être toléré.
(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)))
par exemple.
(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
Ce sont les deux meilleures approches et correctes:
Utilisation de 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")
Cela vous permet de contrôler avec précision le type dans lequel vous souhaitez analyser le nombre, lorsque cela est important pour votre cas d'utilisation.
Utilisation du lecteur Clojure EDN:
(require '[clojure.edn :as edn])
(edn/read-string "333")
Contrairement à l'utilisation de read-string
de clojure.core
qui n'est pas sûr à utiliser sur des entrées non fiables, edn/read-string
est sûr à exécuter sur des entrées non fiables telles que les entrées utilisateur.
C'est souvent plus pratique que l'interop Java si vous n'avez pas besoin d'avoir un contrôle spécifique des types. Il peut analyser n'importe quel littéral numérique que Clojure peut analyser, comme:
;; Ratios
(edn/read-string "22/7")
;; Hexadecimal
(edn/read-string "0xff")
Liste complète ici: https://www.rubberducking.com/2019/05/05/clojure-for-non-clojure-programmers.html#numbers