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

128

J'ai plusieurs chaînes, certaines comme "45", d'autres comme "45px". Comment convertir les deux en nombre 45?

appshare.co
la source
32
Je suis heureux que quelqu'un n'ait pas peur de poser des questions de base.
octopusgrabbus
4
+1 - une partie du défi est que les documents Clojure ne répondent parfois pas à ces questions «basiques» que nous tenons pour acquises dans d'autres langues. (J'ai eu la même question 3 ans plus tard et j'ai trouvé ça).
Glenn
3
@octopusgrabbus - Je serais intéressé de savoir "pourquoi" les gens ont peur de poser des questions de base?
appshare.co
1
@Zubair il est supposé que les choses de base sont déjà expliquées quelque part donc vous avez probablement oublié quelque chose et votre question sera déclassée pour "aucun effort de recherche".
Al.G.
1
Pour ceux qui viennent ici de Google qui cherchent à convertir "9"en 9, c'est la meilleure chose qui a fonctionné pour moi: (Integer. "9").
weltschmerz

Réponses:

79

Cela fonctionnera sur 10pxoupx10

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

il analysera le premier chiffre continu uniquement ainsi

user=> (parse-int "10not123")
10
user=> (parse-int "abc10def11")
10
jhnstn
la source
Bonne réponse! C'est mieux que d'utiliser read-string à mon avis. J'ai changé ma réponse pour utiliser votre technique. J'ai également apporté quelques petits changements.
Benjamin Atkin
cela me donneException in thread "main" java.lang.ClassNotFoundException: Integer.,
maazza
83

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 la chaîne de lecture 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 autoriser les nombres négatifs. Je l'ai également fait pour que le numéro commence au début de la chaîne.

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

De plus, j'ai trouvé que Integer / parseInt analyse comme décimal lorsqu'aucune base n'est donnée, même s'il y a des zéros non significatifs.

Ancienne réponse

Tout d'abord, pour analyser juste un entier (puisqu'il s'agit d'un hit sur google et que ce sont de bonnes informations de base):

Vous pouvez utiliser le lecteur :

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

Vous pouvez vérifier qu'il s'agit d'un nombre après sa lecture:

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

Je ne suis pas sûr que l'entrée utilisateur puisse être approuvée par le lecteur clojure afin que vous puissiez également vérifier avant de la 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, comme 29px:

(read-string (second (re-matches (re-pattern "(\\d+).*") "29px"))) ; => 29
Benjamin Atkin
la source
J'aime mieux votre réponse - dommage que cela ne soit pas fourni par la bibliothèque principale de clojure. Une critique mineure - techniquement, vous ifdevriez être un whencar il n'y a pas d'autre bloc dans votre fns.
quux00
1
Ouais, n'arrêtez pas de lire après le premier ou le deuxième extrait de code!
Benjamin Atkin
2
Un heads-up sur les nombres avec des zéros non significatifs. read-stringles interprète comme octaux: (read-string "08")lève une exception. Integer/valueOfles traite comme des nombres décimaux: donne la valeur (Integer/valueOf "08")8.
rubasov
Notez également que read-stringlève une exception si vous lui donnez une chaîne vide ou quelque chose comme "29px"
Ilya Boyandin
Comme il se doit. J'ai répondu à la question dans le titre et ce à quoi les gens s'attendent lorsqu'ils voient cette page, avant de répondre à la question dans le corps de la question. C'est le dernier extrait de code dans le corps de ma réponse.
Benjamin Atkin
30
(defn parse-int [s]
  (Integer. (re-find #"[0-9]*" s)))

user> (parse-int "10px")
10
user> (parse-int "10")
10
MayDaniel
la source
Merci. Cela m'a aidé à diviser un produit en une séquence de chiffres.
octopusgrabbus
3
Puisque nous sommes dans le pays Java pour cette réponse, il est généralement conseillé d'utiliser Integer/valueOf, plutôt que le constructeur Integer. La classe Integer met en cache les valeurs comprises entre -128 et 127 pour minimiser la création d'objets. Le Javadoc entier décrit cela comme le fait cet article: stackoverflow.com/a/2974852/871012
quux00
15

Cela fonctionne en réponse pour moi, beaucoup plus simple.

(chaîne de lecture "123")

=> 123

f3rz_net
la source
1
Soyez prudent en utilisant ceci avec l'entrée utilisateur. read-stringpeut exécuter du code selon la documentation: clojuredocs.org/clojure.core/read-string
jerney
c'est idéal pour les entrées fiables, comme par exemple un puzzle de programmation. @jerney a raison: veillez à ne pas l'utiliser dans le code réel.
hraban
10

AFAIK 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]" "")))
skuro
la source
Non recommandé. Cela fonctionnera jusqu'à ce que quelqu'un le lance 1.5... et il n'utilise pas non plus la clojure.string/replacefonction intégrée.
tar
8

Ce n'est pas parfait, mais voici quelque chose avec filter, Character/isDigitet Integer/parseInt. Cela ne fonctionnera pas pour les nombres à virgule flottante et é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 meilleure façon 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)
michiakig
la source
4

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

  • Doit commencer par un chiffre
  • Doit tolérer les entrées vides
  • Tolère le passage de n'importe quel 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 bonus pour en faire une méthode multiple qui permet une valeur par défaut fournie par l'utilisateur autre que 0.

Tony K.
la source
4

Extension de 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 lever une exception.

Ma question est de savoir s'il est acceptable d'abréger le nom en "str-> int", ou si des choses comme celle-ci doivent toujours être entièrement spécifiées.

Nick Vargish
la source
3

L'utilisation de la (re-seq)fonction peut également étendre la valeur de retour à une chaîne contenant tous les nombres existants dans la chaîne d'entrée dans l'ordre:

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

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

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


la source
3

La question porte sur l'analyse d'une chaîne en un nombre.

(number? 0.5)
;;=> true

Donc, à partir des décimales ci-dessus, il faut également analyser.

Peut-être ne répond 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 d'un nombre ou non (donc "px" n'est pas autorisé) et laisser l'appelant gérer les non-nombres en renvoyant nil:

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

Et si les flottants sont problématiques pour votre domaine au lieu de Float/parseFloatput bigdecou autre chose.

Chris Murphy
la source
3

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

Utilisation de l'interopérabilité Java:

(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 nombre, lorsque cela compte pour votre cas d'utilisation.

Utilisation du lecteur Clojure EDN:

(require '[clojure.edn :as edn])
(edn/read-string "333")

Contrairement à l'utilisation read-stringde clojure.corequi n'est pas sûre à utiliser sur une entrée non approuvée, elle edn/read-stringpeut être exécutée en toute sécurité sur une entrée non approuvée telle que l'entrée utilisateur.

C'est souvent plus pratique que l'interopérabilité Java si vous n'avez pas besoin de contrôler spécifiquement les types. Il peut analyser n'importe quel nombre littéral que Clojure peut analyser, tel que:

;; 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

Didier A.
la source
2

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 souhaiterez peut-être 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"))))
Alan Thompson
la source
Aussi, juste une question curieuse: pourquoi utilisez-vous (t/refer-tupelo)au lieu de faire faire à l'utilisateur (:require [tupelo.core :refer :all])?
Qwerp-Derp
refer-tupeloest calqué sur le modèle refer-clojure, en ce sens qu'il n'inclut pas tout ce qu'il (:require [tupelo.core :refer :all])fait.
Alan Thompson