web-dev-qa-db-fra.com

Dans Clojure, comment puis-je convertir une chaîne en un nombre?

J'ai différentes chaînes, certaines comme "45", d'autres comme "45px". Comment je convertis les deux au nombre 45?

113
Zubair

Cela fonctionnera sur 10px ou px10 

(defn parse-int [s]
   (Integer. (re-find  #"\d+" s )))

il analysera le premier chiffre continu uniquement pour

user=> (parse-int "10not123")
10
user=> (parse-int "abc10def11")
10
73
jhnstn

Nouvelle réponse

J'aime mieux la réponse de snrobot. L'utilisation de la méthode Java est plus simple et plus robuste que l'utilisation de read-string pour ce cas d'utilisation simple. J'ai fait quelques petits changements. Puisque l'auteur n'a pas exclu les nombres négatifs, je l'ai ajusté pour permettre les nombres négatifs. Je l'ai aussi fait donc il faut que le nombre commence au début de la chaîne.

(defn parse-int [s]
  (Integer/parseInt (re-find #"\A-?\d+" s)))

De plus, j'ai trouvé qu'Integer/parseInt était décimal quand on ne donnait pas de base, même s'il y avait des zéros en tête.

Ancienne réponse

Premièrement, pour analyser seulement un entier (puisqu'il s'agit d'un hit sur google et de bonnes informations de base):

Vous pouvez utiliser le lecteur :

(read-string "9") ; => 9

Vous pouvez vérifier que c'est un numéro après l'avoir lu:

(defn str->int [str] (if (number? (read-string str))))

Je ne suis pas sûr que le lecteur de clojure puisse faire confiance aux entrées de l'utilisateur afin que vous puissiez vérifier également avant de les lire:

(defn str->int [str] (if (re-matches (re-pattern "\\d+") str) (read-string str)))

Je pense que je préfère la dernière solution.

Et maintenant, à votre question spécifique. Pour analyser quelque chose qui commence par un entier, tel que 29px:

(read-string (second (re-matches (re-pattern "(\\d+).*") "29px"))) ; => 29
78
Benjamin Atkin
(defn parse-int [s]
  (Integer. (re-find #"[0-9]*" s)))

user> (parse-int "10px")
10
user> (parse-int "10")
10
28
MayDaniel

Cela fonctionne pour moi, beaucoup plus simple.

(chaîne de lecture "123")

=> 123

14
f3rz_net

Autant que je sache, il n'y a pas de solution standard à votre problème. Je pense que quelque chose comme ce qui suit, qui utilise clojure.contrib.str-utils2/replace , devrait aider:

(defn str2int [txt]
  (Integer/parseInt (replace txt #"[a-zA-Z]" "")))
9
skuro

Ce n'est pas parfait, mais voici quelque chose avec filter, Character/isDigit et Integer/parseInt. Cela ne fonctionnera pas pour les nombres à virgule flottante et il échouera s'il n'y a pas de chiffre dans l'entrée, vous devriez donc probablement le nettoyer. J'espère qu'il existe une façon plus agréable de faire cela qui n'implique pas autant de Java.

user=> (defn strToInt [x] (Integer/parseInt (apply str (filter #(Character/isDigit %) x))))
#'user/strToInt
user=> (strToInt "45px")
45
user=> (strToInt "45")
45
user=> (strToInt "a")
Java.lang.NumberFormatException: For input string: "" (NO_SOURCE_FILE:0)
7
michiakig

La fonction (re-seq) peut également étendre la valeur renvoyée à une chaîne contenant tous les nombres existants dans la chaîne d'entrée, dans l'ordre suivant: 

(defn convert-to-int [s] (->> (re-seq #"\d" s) (apply str) (Integer.)))

(convert-to-int "10not123") => 10123

(type *1) => Java.lang.Integer

2
amir-t

La question concerne l’analyse d’une chaîne en un nombre.

(number? 0.5)
;;=> true

Ainsi, à partir des décimales ci-dessus, il convient également d'analyser. 

Peut-être que je ne réponds pas exactement à la question maintenant, mais pour un usage général, je pense que vous voudriez être strict quant à savoir s’il s’agit ou non d’un nombre (donc "px" non autorisé) et laisser l’appelant gérer les non-nombres en retournant nil:

(defn str->number [x]
  (when-let [num (re-matches #"-?\d+\.?\d*" x)]
    (try
      (Float/parseFloat num)
      (catch Exception _
        nil))))

Et si les flottants posent problème à votre domaine au lieu de Float/parseFloat, mettez bigdec ou quelque chose d'autre.

2
Chris Murphy

Développer la réponse de snrobot:

(defn string->integer [s] 
  (when-let [d (re-find #"-?\d+" s)] (Integer. d)))

Cette version renvoie nil s'il n'y a pas de chiffres dans l'entrée plutôt que de déclencher une exception.

Ma question est de savoir s'il est acceptable d'abréger le nom en "str-> int", ou si de tels éléments devraient toujours être entièrement spécifiés.

2
Nick Vargish

J'ajouterais probablement quelques éléments aux exigences:

  • Doit commencer par un chiffre
  • Doit tolérer des entrées vides
  • Tolère le passage d'un objet (toString est standard)

Peut-être quelque chose comme:

(defn parse-int [v] 
   (try 
     (Integer/parseInt (re-find #"^\d+" (.toString v))) 
     (catch NumberFormatException e 0)))

(parse-int "lkjhasd")
; => 0
(parse-int (Java.awt.Color. 4 5 6))
; => 0
(parse-int "a5v")
; => 0
(parse-int "50px")
; => 50

et puis peut-être des points de bonus pour en faire une multi-méthode qui autorise un défaut fourni par l'utilisateur autre que 0.

1
Tony K.

Pour tous ceux qui cherchent à analyser un littéral chaîne plus normal en un nombre, c'est-à-dire une chaîne ne contenant pas d'autres caractères non numériques. Ce sont les deux meilleures approches:

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 précisément le type dans lequel vous souhaitez analyser le numéro, 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 à partir de clojure.core qui n'est pas sûr pour une entrée non fiable, edn/read-string peut être exécuté en toute sécurité sur des entrées non fiables telles que les entrées utilisateur.

Ceci est souvent plus pratique que l’interopération Java si vous n’avez pas besoin d’un contrôle spécifique des types. Il peut analyser n'importe quel littéral numérique que Clojure peut analyser, par exemple:

;; Ratios
(edn/read-string "22/7")
;; Hexadecimal
(edn/read-string "0xff")

Liste complète ici: https://www.rubberducking.com/2019/05/clojure-for-non-clojure-programmers.html#numbers

0
Didier A.

Pour les cas simples, vous pouvez simplement utiliser une expression régulière pour extraire la première chaîne de chiffres, comme mentionné ci-dessus.

Si vous avez une situation plus compliquée, vous pouvez utiliser la bibliothèque InstaParse:

(ns tst.parse.demo
  (:use tupelo.test)
  (:require
    [clojure.string :as str]
    [instaparse.core :as insta]
    [tupelo.core :as t] ))
(t/refer-tupelo)

(dotest
  (let [abnf-src            "
size-val      = int / int-px
int           = digits          ; ex '123'
int-px        = digits <'px'>   ; ex '123px'
<digits>      = 1*digit         ; 1 or more digits
<digit>       = %x30-39         ; 0-9
"
    tx-map        {:int      (fn fn-int [& args]
                               [:int (Integer/parseInt (str/join args))])
                   :int-px   (fn fn-int-px [& args]
                               [:int-px (Integer/parseInt (str/join args))])
                   :size-val identity
                  }

    parser              (insta/parser abnf-src :input-format :abnf)
    instaparse-failure? (fn [arg] (= (class arg) instaparse.gll.Failure))
    parse-and-transform (fn [text]
                          (let [result (insta/transform tx-map
                                         (parser text))]
                            (if (instaparse-failure? result)
                              (throw (IllegalArgumentException. (str result)))
                              result)))  ]
  (is= [:int 123]     (parse-and-transform "123"))
  (is= [:int-px 123]  (parse-and-transform "123px"))
  (throws?            (parse-and-transform "123xyz"))))
0
Alan Thompson