In Clojure come posso convertire una stringa in un numero?


128

Ho varie stringhe, alcune come "45", altre come "45px". Come posso convertire entrambi al numero 45?


32
Sono contento che qualcuno non abbia paura di porre alcune domande di base.
octopusgrabbus,

4
+1 - parte della sfida è che i documenti Clojure a volte non affrontano queste domande "di base" che diamo per scontate in altre lingue. (Ho avuto la stessa domanda 3 anni dopo e l'ho trovato).
Glenn,

3
@octopusgrabbus - Sarei interessato a sapere "perché" le persone hanno paura di porre domande di base?
appshare.co,

1
@Zubair si suppone che le cose di base siano già state spiegate da qualche parte, quindi molto probabilmente hai trascurato qualcosa e la tua domanda verrà annullata per "nessuna ricerca".
Al.G.

1
Per chi proviene da qui Google cerca di convertire "9"in 9, questa è la cosa migliore che ha funzionato per me: (Integer. "9").
weltschmerz,

Risposte:


79

Questo funzionerà su 10pxopx10

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

analizzerà solo la prima cifra continua

user=> (parse-int "10not123")
10
user=> (parse-int "abc10def11")
10

Bella risposta! Questo è meglio che usare read-string secondo me. Ho cambiato la mia risposta per usare la tua tecnica. Ho anche apportato un paio di piccole modifiche.
Benjamin Atkin,

questo mi dàException in thread "main" java.lang.ClassNotFoundException: Integer.,
maazza

83

Nuova risposta

Mi piace la risposta di Snrobot meglio. L'uso del metodo Java è più semplice e più robusto rispetto all'utilizzo della stringa di lettura per questo semplice caso d'uso. Ho fatto un paio di piccoli cambiamenti. Poiché l'autore non ha escluso i numeri negativi, l'ho modificato per consentire i numeri negativi. Ho anche fatto in modo che richieda il numero per iniziare all'inizio della stringa.

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

Inoltre ho scoperto che Integer / parseInt viene analizzato come decimale quando non viene fornita alcuna radice, anche se ci sono zero iniziali.

Vecchia risposta

Innanzitutto, per analizzare solo un numero intero (poiché si tratta di un hit su google ed è una buona informazione di base):

Puoi usare il lettore :

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

Puoi verificare che sia un numero dopo che è stato letto:

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

Non sono sicuro che l'input dell'utente possa essere considerato attendibile dal lettore di clojure in modo da poter verificare anche prima che venga letto:

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

Penso di preferire l'ultima soluzione.

E ora, alla tua domanda specifica. Per analizzare qualcosa che inizia con un numero intero, come 29px:

(read-string (second (re-matches (re-pattern "(\\d+).*") "29px"))) ; => 29

Mi piace di più la tua risposta, peccato che non sia fornita dalla libreria principale di clojure. Una piccola critica: tecnicamente ifdovresti esserlo whenperché non ci sono altri blocchi nelle tue reti.
quux00,

1
Sì, per favore non smettere di leggere dopo il primo o il secondo frammento di codice!
Benjamin Atkin,

2
Un heads-up sui numeri con zeri iniziali. read-stringli interpreta come ottale: (read-string "08")genera un'eccezione. Integer/valueOfli considera come decimali: (Integer/valueOf "08")valuta 8.
rubasov

Nota anche che read-stringgenera un'eccezione se le dai una stringa vuota o qualcosa del genere "29px"
Ilya Boyandin

Come dovrebbe. Ho risposto alla domanda nel titolo e cosa si aspettano le persone quando vedono questa pagina, prima di rispondere alla domanda nel corpo della domanda. È l'ultimo frammento di codice nel corpo della mia risposta.
Benjamin Atkin,

30
(defn parse-int [s]
  (Integer. (re-find #"[0-9]*" s)))

user> (parse-int "10px")
10
user> (parse-int "10")
10

Grazie. Questo mi è stato utile nel dividere un prodotto in una sequenza di cifre.
octopusgrabbus,

3
Dato che siamo nella terra di Java per questa risposta, è generalmente consigliabile utilizzare Integer/valueOf, piuttosto che il costruttore Integer. La classe Integer memorizza nella cache valori compresi tra -128 e 127 per ridurre al minimo la creazione di oggetti. L'intero Javadoc descrive questo come fa questo post: stackoverflow.com/a/2974852/871012
quux00

15

Questo funziona in cambio di me, molto più semplice.

(stringa di lettura "123")

=> 123


1
Fai attenzione usando questo con l'input dell'utente. read-stringpuò eseguire il codice in base ai documenti: clojuredocs.org/clojure.core/read-string
jerney

questo è ottimo per input affidabili, come ad esempio un puzzle di programmazione. @jerney ha ragione: fai attenzione a non usarlo nel codice reale.
hraban

10

AFAIK non esiste una soluzione standard per il tuo problema. Penso che qualcosa di simile al seguente, che utilizza clojure.contrib.str-utils2/replace, dovrebbe aiutare:

(defn str2int [txt]
  (Integer/parseInt (replace txt #"[a-zA-Z]" "")))

Non consigliato. Funzionerà fino a quando qualcuno non lo lancia 1.5... e inoltre non utilizza la clojure.string/replacefunzione integrata.
tar

8

Questo non è perfetto, ma ecco qualcosa con filter, Character/isDigite Integer/parseInt. Non funzionerà con numeri a virgola mobile e non funzionerà se non ci sono cifre nell'input, quindi probabilmente dovresti ripulirlo. Spero che ci sia un modo migliore di farlo che non coinvolga così tanto 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)

4

Vorrei probabilmente aggiungere alcune cose ai requisiti:

  • Deve iniziare con una cifra
  • Deve tollerare input vuoti
  • Tollera il passaggio di qualsiasi oggetto (toString è standard)

Forse qualcosa del tipo:

(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

e quindi forse punti bonus per rendere questo un multi-metodo che consente un valore predefinito fornito dall'utente diverso da 0.


4

Espandendo la risposta di Snrobot:

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

Questa versione restituisce zero se non ci sono cifre nell'input, piuttosto che sollevare un'eccezione.

La mia domanda è se sia accettabile abbreviare il nome in "str-> int", o se cose come questa debbano sempre essere specificate per intero.


3

Anche l'uso della (re-seq)funzione può estendere il valore restituito a una stringa contenente tutti i numeri esistenti nella stringa di input in ordine:

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

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

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


3

La domanda si pone sull'analisi di una stringa in un numero.

(number? 0.5)
;;=> true

Quindi dai decimali sopra dovrebbero essere analizzati anche.

Forse non rispondo esattamente alla domanda ora, ma per uso generale penso che vorresti essere severo sul fatto che sia un numero o meno (quindi "px" non è consentito) e lasciare che il chiamante gestisca i non-numeri restituendo zero:

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

E se i float sono problematici per il tuo dominio anziché Float/parseFloatput bigdeco qualcos'altro.


3

Per chiunque cerchi di analizzare un valore letterale String più normale in un numero, ovvero una stringa che non ha altri caratteri non numerici. Questi sono i due migliori approcci:

Utilizzando l'interoperabilità 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")

Ciò consente di controllare con precisione il tipo in cui si desidera analizzare il numero, quando ciò è importante per il proprio caso d'uso.

Utilizzando il lettore EDN Clojure:

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

A differenza dell'utilizzo read-stringda clojure.corecui non è sicuro utilizzare input non attendibili, edn/read-stringè possibile eseguire input non attendibili come l'input dell'utente.

Questo è spesso più conveniente dell'interoperabilità Java se non è necessario avere un controllo specifico dei tipi. Può analizzare qualsiasi numero letterale che Clojure può analizzare come:

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

Elenco completo qui: https://www.rubberducking.com/2019/05/clojure-for-non-clojure-programmers.html#numbers


2

Per casi semplici puoi semplicemente usare una regex per estrarre la prima stringa di cifre come menzionato sopra.

Se hai una situazione più complicata, potresti voler usare la libreria 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"))))

Inoltre, solo una domanda curiosa: perché usi (t/refer-tupelo)invece di far fare all'utente (:require [tupelo.core :refer :all])?
Qwerp-Derp,

refer-tupeloè modellato dopo refer-clojure, in quanto non include tutto ciò che (:require [tupelo.core :refer :all])fa.
Alan Thompson,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.