Mam różne ciągi, niektóre jak „45”, inne jak „45px”. Jak przekonwertować oba te elementy na liczbę 45?
"9"
się 9
, jest to najlepsza rzecz, że pracował dla mnie (Integer. "9")
.
Mam różne ciągi, niektóre jak „45”, inne jak „45px”. Jak przekonwertować oba te elementy na liczbę 45?
"9"
się 9
, jest to najlepsza rzecz, że pracował dla mnie (Integer. "9")
.
Odpowiedzi:
To zadziała na 10px
lubpx10
(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
Exception in thread "main" java.lang.ClassNotFoundException: Integer.,
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.
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
if
powinieneś być a, when
ponieważ w twoich fns nie ma innego bloku.
read-string
interpretuje je jako ósemkowe: (read-string "08")
zgłasza wyjątek. Integer/valueOf
traktuje je jako dziesiętne: (Integer/valueOf "08")
read-string
zgłasza wyjątek, jeśli podasz mu pusty ciąg lub coś w rodzaju „29px”
(defn parse-int [s]
(Integer. (re-find #"[0-9]*" s)))
user> (parse-int "10px")
10
user> (parse-int "10")
10
Integer/valueOf
zamiast 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
Działa to w odpowiedzi dla mnie, znacznie prostsze.
(ciąg do odczytu „123”)
=> 123
read-string
może wykonać kod zgodnie z dokumentacją: clojuredocs.org/clojure.core/read-string
AFAIK nie ma standardowego rozwiązania Twojego problemu. Myślę, że clojure.contrib.str-utils2/replace
powinno pomóc coś takiego jak poniższe, które używa :
(defn str2int [txt]
(Integer/parseInt (replace txt #"[a-zA-Z]" "")))
1.5
w nią nie rzuci ... a także nie korzysta z wbudowanej clojure.string/replace
funkcji.
To nie jest idealne, ale tutaj jest coś filter
, Character/isDigit
i 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)
Dodałbym pewnie kilka rzeczy do wymagań:
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.
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.
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
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/parseFloat
wstawiania bigdec
lub czegoś innego.
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-string
z clojure.core
którego nie jest bezpieczne w przypadku niezaufanych danych wejściowych, edn/read-string
moż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
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"))))
(t/refer-tupelo)
zamiast nakłaniać użytkownika do działania (:require [tupelo.core :refer :all])
?
refer-tupelo
jest wzorowany na tym refer-clojure
, że nie obejmuje wszystkiego, co (:require [tupelo.core :refer :all])
robi.