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?


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

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

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

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


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

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)

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.


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.


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


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.


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


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

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
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.