W Clojure jak przekonwertować ciąg znaków na liczbę?

128

Mam różne ciągi, niektóre jak „45”, inne jak „45px”. Jak przekonwertować oba te elementy na liczbę 45?

appshare.co
źródło
32
Cieszę się, że ktoś nie boi się zadać kilku podstawowych pytań.
octopusgrabbus
4
+1 - częścią wyzwania jest to, że dokumenty Clojure czasami nie odnoszą się do tych „podstawowych” pytań, które w innych językach przyjmujemy za pewnik. (Miałem to samo pytanie 3 lata później i znalazłem to).
Glenn
3
@octopusgrabbus - chciałbym wiedzieć, „dlaczego” ludzie boją się zadawać podstawowe pytania?
appshare.co
1
@Zubair przypuszcza się, że podstawowe rzeczy są już gdzieś wyjaśnione, więc najprawdopodobniej coś przeoczyłeś, a twoje pytanie zostanie odrzucone za „brak wysiłku badawczego”.
Al.G.
1
Dla tych, którzy przyjeżdżają tu z Google chce przekształcić "9"się 9, jest to najlepsza rzecz, że pracował dla mnie (Integer. "9").
weltschmerz

Odpowiedzi:

79

To zadziała na 10pxlubpx10

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

przeanalizuje tylko pierwszą ciągłą cyfrę

user=> (parse-int "10not123")
10
user=> (parse-int "abc10def11")
10
jhnstn
źródło
Niezła odpowiedź! Moim zdaniem jest to lepsze niż używanie ciągu do odczytu. Zmieniłem odpowiedź, żeby użyć twojej techniki. Wprowadziłem również kilka drobnych zmian.
Benjamin Atkin
to daje miException in thread "main" java.lang.ClassNotFoundException: Integer.,
maazza
83

Nowa odpowiedź

Odpowiedź snrobota podoba mi się bardziej. Użycie metody Java jest prostsze i bardziej niezawodne niż użycie ciągu do odczytu w tym prostym przypadku użycia. Zrobiłem kilka drobnych zmian. Ponieważ autor nie wykluczył liczb ujemnych, dostosowałem to, aby dopuszczać liczby ujemne. Zrobiłem to również, więc wymaga, aby numer zaczynał się na początku ciągu.

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

Dodatkowo odkryłem, że Integer / parseInt analizuje jako dziesiętne, gdy nie jest podana żadna podstawa, nawet jeśli występują zera wiodące.

Stara odpowiedź

Po pierwsze, aby przeanalizować tylko liczbę całkowitą (ponieważ jest to hit w Google i dobre informacje w tle):

Możesz użyć czytnika :

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

Możesz sprawdzić, czy jest to liczba po przeczytaniu:

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

Nie jestem pewien, czy dane wprowadzane przez użytkownika mogą być zaufane przez czytnik clojure, więc możesz sprawdzić przed przeczytaniem:

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

Myślę, że wolę ostatnie rozwiązanie.

A teraz do twojego konkretnego pytania. Aby przeanalizować coś, co zaczyna się od liczby całkowitej, na przykład 29px:

(read-string (second (re-matches (re-pattern "(\\d+).*") "29px"))) ; => 29
Benjamin Atkin
źródło
Najbardziej podoba mi się twoja odpowiedź - szkoda, że ​​nie zapewnia tego biblioteka rdzenia clojure. Jedna drobna krytyka - technicznie rzecz biorąc, ifpowinieneś być a, whenponieważ w twoich fns nie ma innego bloku.
quux00
1
Tak, nie przestawaj czytać po pierwszym lub drugim fragmencie kodu!
Benjamin Atkin
2
Heads-up z liczbami z zerami na początku. read-stringinterpretuje je jako ósemkowe: (read-string "08")zgłasza wyjątek. Integer/valueOftraktuje je jako dziesiętne: (Integer/valueOf "08")
zwraca
Zauważ również, że read-stringzgłasza wyjątek, jeśli podasz mu pusty ciąg lub coś w rodzaju „29px”
Ilya Boyandin
Jak powinno. Odpowiedziałem na pytanie w tytule i czego ludzie oczekują, gdy zobaczą tę stronę, zanim odpowiedziałem na pytanie w treści pytania. To ostatni fragment kodu w treści mojej odpowiedzi.
Benjamin Atkin
30
(defn parse-int [s]
  (Integer. (re-find #"[0-9]*" s)))

user> (parse-int "10px")
10
user> (parse-int "10")
10
MayDaniel
źródło
Dzięki. Pomogło mi to w podzieleniu produktu na ciąg cyfr.
octopusgrabbus
3
Ponieważ jesteśmy w krainie Java, aby uzyskać tę odpowiedź, ogólnie zaleca się użycie Integer/valueOfzamiast konstruktora Integer. Klasa Integer buforuje wartości od -128 do 127, aby zminimalizować tworzenie obiektów. Integer Javadoc opisuje to, podobnie jak ten post: stackoverflow.com/a/2974852/871012
quux00
15

Działa to w odpowiedzi dla mnie, znacznie prostsze.

(ciąg do odczytu „123”)

=> 123

f3rz_net
źródło
1
Uważaj, używając tego przy wprowadzaniu danych przez użytkownika. read-stringmoże wykonać kod zgodnie z dokumentacją: clojuredocs.org/clojure.core/read-string
jerney
jest to świetne rozwiązanie w przypadku zaufanych danych wejściowych, na przykład układanki programistycznej. @jerney ma rację: uważaj, aby nie używać go w rzeczywistym kodzie.
hraban
10

AFAIK nie ma standardowego rozwiązania Twojego problemu. Myślę, że clojure.contrib.str-utils2/replacepowinno pomóc coś takiego jak poniższe, które używa :

(defn str2int [txt]
  (Integer/parseInt (replace txt #"[a-zA-Z]" "")))
skuro
źródło
Niepolecane. Będzie działać, dopóki ktoś 1.5w nią nie rzuci ... a także nie korzysta z wbudowanej clojure.string/replacefunkcji.
tar
8

To nie jest idealne, ale tutaj jest coś filter, Character/isDigiti Integer/parseInt. Nie będzie działać dla liczb zmiennoprzecinkowych i nie powiedzie się, jeśli na wejściu nie ma cyfry, więc prawdopodobnie powinieneś ją wyczyścić. Mam nadzieję, że istnieje lepszy sposób na zrobienie tego, który nie wymaga tak dużo 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
źródło
4

Dodałbym pewnie kilka rzeczy do wymagań:

  • Musi zaczynać się od cyfry
  • Musi tolerować puste dane wejściowe
  • Toleruje przekazywanie dowolnego obiektu (toString jest standardem)

Może coś takiego:

(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

a następnie być może dodatkowe punkty za uczynienie z tego metody wielu metod, która pozwala na domyślne ustawienie użytkownika inne niż 0.

Tony K.
źródło
4

Rozwinięcie odpowiedzi snrobota:

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

Ta wersja zwraca nil, jeśli w danych wejściowych nie ma cyfr, zamiast zgłaszać wyjątek.

Moje pytanie brzmi, czy dopuszczalne jest skrócenie nazwy do „str-> int”, czy też takie rzeczy powinny być zawsze w pełni określone.

Nick Vargish
źródło
3

Również użycie (re-seq)funkcji może rozszerzyć zwracaną wartość do ciągu zawierającego wszystkie liczby istniejące w ciągu wejściowym w kolejności:

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

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

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


źródło
3

Pytanie dotyczy parsowania łańcucha na liczbę.

(number? 0.5)
;;=> true

Tak więc z powyższych liczb dziesiętnych należy również przeanalizować.

Być może nie do końca odpowiadając teraz na pytanie, ale myślę, że do ogólnego użytku chciałbyś być ścisły co do tego, czy jest to liczba, czy nie (więc „px” jest niedozwolone) i pozwól dzwoniącemu obsłużyć inne niż liczby, zwracając zero:

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

A jeśli zmiennoprzecinkowe są problematyczne dla Twojej domeny, zamiast Float/parseFloatwstawiania bigdeclub czegoś innego.

Chris Murphy
źródło
3

Dla każdego, kto chce przeanalizować bardziej normalny literał String na liczbę, to znaczy ciąg, który nie ma innych znaków nienumerycznych. Oto dwa najlepsze podejścia:

Korzystanie z współpracy w języku 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")

Pozwala to precyzyjnie kontrolować typ, w którym chcesz przeanalizować liczbę, gdy ma to znaczenie dla twojego przypadku użycia.

Korzystanie z czytnika Clojure EDN:

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

W przeciwieństwie do korzystania read-stringz clojure.corektórego nie jest bezpieczne w przypadku niezaufanych danych wejściowych, edn/read-stringmożna bezpiecznie uruchomić na niezaufanych danych wejściowych, takich jak dane wejściowe użytkownika.

Jest to często wygodniejsze niż współdziałanie języka Java, jeśli nie potrzebujesz określonej kontroli nad typami. Może analizować dowolny literał liczbowy, który Clojure może przeanalizować, taki jak:

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

Pełna lista tutaj: https://www.rubberducking.com/2019/05/clojure-for-non-clojure-programmers.html#numbers

Didier A.
źródło
2

W prostych przypadkach możesz po prostu użyć wyrażenia regularnego, aby wyciągnąć pierwszy ciąg cyfr, jak wspomniano powyżej.

Jeśli masz bardziej skomplikowaną sytuację, możesz skorzystać z biblioteki 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
źródło
I tylko ciekawe pytanie: dlaczego używasz (t/refer-tupelo)zamiast nakłaniać użytkownika do działania (:require [tupelo.core :refer :all])?
Qwerp-Derp
refer-tupelojest wzorowany na tym refer-clojure, że nie obejmuje wszystkiego, co (:require [tupelo.core :refer :all])robi.
Alan Thompson