Wie kann ich in Clojure einen String in eine Zahl konvertieren?

128

Ich habe verschiedene Zeichenfolgen, einige wie "45", einige wie "45px". Wie konvertiere ich beide in die Zahl 45?

appshare.co
quelle
32
Ich bin froh, dass jemand keine Angst hat, einige grundlegende Fragen zu stellen.
Octopusgrabbus
4
+1 - Teil der Herausforderung ist, dass die Clojure-Dokumente diese "grundlegenden" Fragen, die wir in anderen Sprachen für selbstverständlich halten, manchmal nicht beantworten. (Ich hatte die gleiche Frage 3 Jahre später und fand diese).
Glenn
3
@octopusgrabbus - Ich würde gerne wissen, warum Menschen Angst haben, grundlegende Fragen zu stellen.
appshare.co
1
@Zubair Es wird angenommen, dass grundlegende Dinge bereits irgendwo erklärt wurden, sodass Sie höchstwahrscheinlich etwas übersehen haben und Ihre Frage für "kein Forschungsaufwand" herabgestuft wird.
Al.G.
1
Für diejenigen, die von Google hierher kommen und in konvertieren "9"möchten 9, ist dies das Beste, was für mich funktioniert hat : (Integer. "9").
Weltschmerz

Antworten:

79

Dies wird auf 10pxoder funktionierenpx10

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

es wird nur die erste fortlaufende Ziffer so analysiert

user=> (parse-int "10not123")
10
user=> (parse-int "abc10def11")
10
jhnstn
quelle
Gute Antwort! Dies ist meiner Meinung nach besser als die Verwendung von Lesezeichenfolgen. Ich habe meine Antwort geändert, um Ihre Technik zu verwenden. Ich habe auch ein paar kleine Änderungen vorgenommen.
Benjamin Atkin
das gibt mirException in thread "main" java.lang.ClassNotFoundException: Integer.,
maazza
83

Neue Antwort

Ich mag die Antwort von Snrobot besser. Die Verwendung der Java-Methode ist einfacher und robuster als die Verwendung von Lesezeichenfolgen für diesen einfachen Anwendungsfall. Ich habe ein paar kleine Änderungen vorgenommen. Da der Autor negative Zahlen nicht ausschloss, habe ich sie angepasst, um negative Zahlen zuzulassen. Ich habe es auch so gemacht, dass die Nummer am Anfang der Zeichenfolge beginnen muss.

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

Außerdem habe ich festgestellt, dass Integer / parseInt als Dezimalzahl analysiert wird, wenn kein Radix angegeben wird, selbst wenn führende Nullen vorhanden sind.

Alte Antwort

Erstens, um nur eine Ganzzahl zu analysieren (da dies ein Hit bei Google ist und es sich um gute Hintergrundinformationen handelt):

Sie könnten den Leser benutzen :

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

Sie können überprüfen, ob es sich um eine Zahl handelt, nachdem sie gelesen wurde:

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

Ich bin mir nicht sicher, ob Benutzereingaben vom Clojure-Reader als vertrauenswürdig eingestuft werden können, sodass Sie sie auch vor dem Lesen überprüfen können:

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

Ich glaube, ich bevorzuge die letzte Lösung.

Und nun zu Ihrer spezifischen Frage. So analysieren Sie etwas, das mit einer Ganzzahl beginnt, wie 29px:

(read-string (second (re-matches (re-pattern "(\\d+).*") "29px"))) ; => 29
Benjamin Atkin
quelle
Ihre Antwort gefällt mir am besten - schade, dass dies nicht von der Clojure-Kernbibliothek bereitgestellt wird. Eine kleine Kritik - technisch gesehen ifsollten Sie eine sein, whenda es keinen anderen Block in Ihrer FNS gibt.
Quux00
1
Ja, bitte hören Sie nicht auf, nach dem ersten oder zweiten Codefragment zu lesen!
Benjamin Atkin
2
Ein Heads-up zu Zahlen mit führenden Nullen. read-stringinterpretiert sie als oktal: (read-string "08")löst eine Ausnahme aus. Integer/valueOfbehandelt sie als Dezimalzahl: (Integer/valueOf "08")
ergibt
Beachten Sie auch, dass read-stringeine Ausnahme ausgelöst wird, wenn Sie eine leere Zeichenfolge oder etwas wie "29px"
Ilya Boyandin
So wie es sollte. Ich habe die Frage im Titel beantwortet und was die Leute erwarten, wenn sie diese Seite sehen, bevor ich die Frage im Fragenkörper beantwortete. Es ist das letzte Code-Snippet in meiner Antwort.
Benjamin Atkin
30
(defn parse-int [s]
  (Integer. (re-find #"[0-9]*" s)))

user> (parse-int "10px")
10
user> (parse-int "10")
10
MayDaniel
quelle
Vielen Dank. Dies war hilfreich, um ein Produkt in eine Folge von Ziffern aufzuteilen.
Octopusgrabbus
3
Da wir uns für diese Antwort im Java-Land befinden, ist es im Allgemeinen ratsam Integer/valueOf, anstelle des Integer-Konstruktors zu verwenden. Die Integer-Klasse speichert Werte zwischen -128 und 127 zwischen, um die Objekterstellung zu minimieren. Das Integer Javadoc beschreibt dies ebenso wie diesen Beitrag: stackoverflow.com/a/2974852/871012
quux00
15

Dies funktioniert in Repl für mich viel einfacher.

(Lesezeichenfolge "123")

=> 123

f3rz_net
quelle
1
Seien Sie vorsichtig bei der Benutzereingabe. read-stringkann Code gemäß den Dokumenten ausführen: clojuredocs.org/clojure.core/read-string
jerney
Dies ist ideal für vertrauenswürdige Eingaben, wie z. B. ein Programmierpuzzle. @jerney ist richtig: Achten Sie darauf, es nicht im eigentlichen Code zu verwenden.
Hraban
10

AFAIK gibt es keine Standardlösung für Ihr Problem. Ich denke, etwas wie das Folgende, das verwendet clojure.contrib.str-utils2/replace, sollte helfen:

(defn str2int [txt]
  (Integer/parseInt (replace txt #"[a-zA-Z]" "")))
Skuro
quelle
Nicht empfohlen. Es wird funktionieren, bis jemand darauf wirft 1.5... und es nutzt auch nicht die eingebaute clojure.string/replaceFunktion.
Teer
8

Dies ist nicht perfekt, aber hier ist etwas mit filter, Character/isDigitund Integer/parseInt. Es funktioniert nicht für Gleitkommazahlen und schlägt fehl, wenn die Eingabe keine Ziffer enthält. Sie sollten sie daher wahrscheinlich bereinigen. Ich hoffe, es gibt einen schöneren Weg, der nicht so viel Java beinhaltet.

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
quelle
4

Ich würde wahrscheinlich ein paar Dinge zu den Anforderungen hinzufügen:

  • Muss mit einer Ziffer beginnen
  • Muss leere Eingaben tolerieren
  • Toleriert die Übergabe eines Objekts (toString ist Standard)

Vielleicht so etwas wie:

(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

und dann vielleicht Bonuspunkte dafür, dass dies eine Multi-Methode ist, die einen vom Benutzer angegebenen Standardwert außer 0 zulässt.

Tony K.
quelle
4

Die Antwort von Snrobot erweitern:

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

Diese Version gibt null zurück, wenn die Eingabe keine Ziffern enthält, anstatt eine Ausnahme auszulösen.

Meine Frage ist, ob es akzeptabel ist, den Namen mit "str-> int" abzukürzen, oder ob solche Dinge immer vollständig spezifiziert werden sollten.

Nick Vargish
quelle
3

Durch die Verwendung der (re-seq)Funktion kann der Rückgabewert auch auf eine Zeichenfolge erweitert werden, die alle in der Eingabezeichenfolge vorhandenen Zahlen in der folgenden Reihenfolge enthält:

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

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

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


quelle
3

Bei der Frage geht es darum, eine Zeichenfolge in eine Zahl zu analysieren.

(number? 0.5)
;;=> true

Aus den obigen Dezimalstellen sollte also auch analysiert werden.

Vielleicht beantworten Sie die Frage jetzt nicht genau, aber für den allgemeinen Gebrauch sollten Sie genau wissen, ob es sich um eine Nummer handelt oder nicht (daher ist "px" nicht zulässig) und den Anrufer mit Nicht-Nummern umgehen lassen, indem Sie nil zurückgeben:

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

Und wenn Floats für Ihre Domain problematisch sind, anstatt zu Float/parseFloatsetzen bigdecoder etwas anderes.

Chris Murphy
quelle
3

Für alle anderen, die ein normaleres String-Literal in eine Zahl analysieren möchten, dh einen String, der keine anderen nicht numerischen Zeichen enthält. Dies sind die beiden besten Ansätze:

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

Auf diese Weise können Sie den Typ, in dem Sie die Nummer analysieren möchten, genau steuern, wenn dies für Ihren Anwendungsfall von Bedeutung ist.

Verwenden des Clojure EDN-Lesegeräts:

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

Im Gegensatz zur Verwendung, read-stringbei der clojure.coredie Verwendung bei nicht vertrauenswürdigen Eingaben nicht sicher ist, kann sie bei nicht edn/read-stringvertrauenswürdigen Eingaben wie Benutzereingaben sicher ausgeführt werden.

Dies ist häufig praktischer als das Java-Interop, wenn Sie keine spezifische Kontrolle über die Typen benötigen. Es kann jedes Zahlenliteral analysieren, das Clojure analysieren kann, wie z.

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

Vollständige Liste hier: https://www.rubberducking.com/2019/05/clojure-for-non-clojure-programmers.html#numbers

Didier A.
quelle
2

In einfachen Fällen können Sie einfach eine Regex verwenden, um die erste Ziffernfolge wie oben erwähnt herauszuziehen.

Wenn Sie eine kompliziertere Situation haben, können Sie die InstaParse-Bibliothek verwenden:

(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
quelle
Auch nur eine merkwürdige Frage: Warum verwenden Sie, (t/refer-tupelo)anstatt den Benutzer dazu zu bringen (:require [tupelo.core :refer :all])?
Qwerp-Derp
refer-tupeloist nachempfunden refer-clojure, da es nicht alles enthält, was der Weg (:require [tupelo.core :refer :all])tut.
Alan Thompson