En Clojure, ¿cómo puedo convertir una cadena en un número?

128

Tengo varias cadenas, algunas como "45", otras como "45px". ¿Cómo convierto ambos al número 45?

appshare.co
fuente
32
Me alegra que alguien no tenga miedo de hacer algunas preguntas básicas.
octopusgrabbus
44
+1: parte del desafío es que los documentos de Clojure a veces no abordan estas preguntas "básicas" que damos por sentado en otros idiomas. (Tuve la misma pregunta 3 años después y encontré esto).
Glenn
3
@octopusgrabbus - ¿Me interesaría saber "por qué" la gente tiene miedo de hacer preguntas básicas?
appshare.co
1
@Zubair se supone que las cosas básicas ya se explican en alguna parte, por lo que probablemente pasó por alto algo y su pregunta será rechazada por "ningún esfuerzo de investigación".
Al.G.
1
Para los que vienen aquí desde Google busca convertir "9"en 9, esto es lo mejor que funcionó para mí: (Integer. "9").
weltschmerz

Respuestas:

79

Esto funcionará 10pxopx10

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

analizará el primer dígito continuo solo para

user=> (parse-int "10not123")
10
user=> (parse-int "abc10def11")
10
jhnstn
fuente
¡Buena respuesta! Esto es mejor que usar read-string en mi opinión. Cambié mi respuesta para usar tu técnica. Hice un par de pequeños cambios también.
Benjamin Atkin
esto me daException in thread "main" java.lang.ClassNotFoundException: Integer.,
maazza
83

Nueva respuesta

Me gusta más la respuesta de snrobot. Usar el método Java es más simple y más robusto que usar read-string para este caso de uso simple. Hice un par de pequeños cambios. Como el autor no descartó números negativos, lo ajusté para permitir números negativos. También lo hice para que requiera que el número comience al principio de la cadena.

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

Además, descubrí que Integer / parseInt se analiza como decimal cuando no se proporciona una raíz, incluso si hay ceros a la izquierda.

Vieja respuesta

Primero, para analizar solo un número entero (ya que este es un éxito en Google y es una buena información de fondo):

Podrías usar el lector :

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

Puede verificar que es un número después de leerlo:

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

No estoy seguro de si el lector clojure puede confiar en la entrada del usuario para que pueda verificar antes de leerla también:

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

Creo que prefiero la última solución.

Y ahora, a su pregunta específica. Para analizar algo que comienza con un número entero, como 29px:

(read-string (second (re-matches (re-pattern "(\\d+).*") "29px"))) ; => 29
Benjamin Atkin
fuente
Me gusta su respuesta mejor, lástima que esto no sea proporcionado por la biblioteca central de clojure. Una crítica menor: técnicamente ifdebería ser una, whenya que no hay más bloqueos en sus aletas.
quux00
1
Sí, ¡no dejes de leer después del primer o segundo fragmento de código!
Benjamin Atkin
2
Un aviso sobre los números con ceros a la izquierda. read-stringlos interpreta como octales: (read-string "08")arroja una excepción. Integer/valueOflos trata como decimales: (Integer/valueOf "08")evalúa a 8.
rubasov
Tenga en cuenta también que read-stringarroja una excepción si le da una cadena vacía o algo así como "29px"
Ilya Boyandin
Como debería. Respondí la pregunta en el título, y qué esperan las personas cuando ven esta página, antes de responder la pregunta en el cuerpo de la pregunta. Es el último fragmento de código en el cuerpo de mi respuesta.
Benjamin Atkin
30
(defn parse-int [s]
  (Integer. (re-find #"[0-9]*" s)))

user> (parse-int "10px")
10
user> (parse-int "10")
10
MayDaniel
fuente
Gracias. Esto fue útil para dividir un producto en una secuencia de dígitos.
octopusgrabbus
3
Dado que estamos en tierra de Java para esta respuesta, generalmente es aconsejable usar Integer/valueOf, en lugar del constructor Integer. La clase Integer almacena en caché valores entre -128 y 127 para minimizar la creación de objetos. El Javadoc Integer describe esto al igual que esta publicación: stackoverflow.com/a/2974852/871012
quux00 el
15

Esto funciona en respuesta para mí, mucho más directo.

(cadena de lectura "123")

=> 123

f3rz_net
fuente
1
Tenga cuidado al usar esto con la entrada del usuario. read-stringpuede ejecutar código según los documentos: clojuredocs.org/clojure.core/read-string
jerney
Esto es ideal para entradas confiables, como por ejemplo un rompecabezas de programación. @jerney tiene razón: tenga cuidado de no usarlo en el código real.
hraban
10

AFAIK no hay una solución estándar para su problema. Creo que algo como lo siguiente, que utiliza clojure.contrib.str-utils2/replace, debería ayudar:

(defn str2int [txt]
  (Integer/parseInt (replace txt #"[a-zA-Z]" "")))
skuro
fuente
No recomendado. Funcionará hasta que alguien la arroje 1.5... y tampoco hace uso de la clojure.string/replacefunción incorporada.
alquitrán el
8

Esto no es perfecta, pero aquí hay algo con filter, Character/isDigity Integer/parseInt. No funcionará para números de coma flotante y falla si no hay un dígito en la entrada, por lo que probablemente debería limpiarlo. Espero que haya una mejor manera de hacer esto que no implique 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)
michiakig
fuente
4

Probablemente agregaría algunas cosas a los requisitos:

  • Tiene que comenzar con un dígito
  • Tiene que tolerar entradas vacías.
  • Tolera que se pase cualquier objeto (toString es estándar)

Tal vez algo como:

(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

y luego quizás puntos de bonificación por hacer de este un método múltiple que permita un valor predeterminado proporcionado por el usuario que no sea 0.

Tony K.
fuente
4

Ampliando la respuesta de snrobot:

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

Esta versión devuelve nil si no hay dígitos en la entrada, en lugar de generar una excepción.

Mi pregunta es si es aceptable abreviar el nombre a "str-> int", o si cosas como esta siempre deben especificarse por completo.

Nick Vargish
fuente
3

El uso de la (re-seq)función también puede extender el valor de retorno a una cadena que contiene todos los números existentes en la cadena de entrada en orden:

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

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

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


fuente
3

La pregunta se refiere a analizar una cadena en un número.

(number? 0.5)
;;=> true

Entonces, a partir de los decimales anteriores, también se deben analizar.

Tal vez no responda exactamente la pregunta ahora, pero para uso general, creo que le gustaría ser estricto acerca de si es un número o no (por lo tanto, "px" no está permitido) y dejar que la persona que llama maneje no números devolviendo nil:

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

Y si los flotadores son problemáticos para su dominio en lugar de Float/parseFloatput bigdecu otra cosa.

Chris Murphy
fuente
3

Para cualquier otra persona que busque analizar un literal de cadena más normal en un número, es decir, una cadena que no tenga otros caracteres no numéricos. Estos son los dos mejores enfoques:

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

Esto le permite controlar con precisión el tipo en el que desea analizar el número, cuando eso es importante para su caso de uso.

Usando el lector Clojure EDN:

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

A diferencia del uso read-stringdesde el clojure.corecual no es seguro usarlo en una entrada no confiable, edn/read-stringes seguro ejecutarlo en una entrada no confiable, como la entrada del usuario.

Esto suele ser más conveniente que la interoperabilidad de Java si no necesita tener un control específico de los tipos. Puede analizar cualquier número literal que Clojure puede analizar como:

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

Lista completa aquí: https://www.rubberducking.com/2019/05/clojure-for-non-clojure-programmers.html#numbers

Didier A.
fuente
2

Para casos simples, puede usar una expresión regular para extraer la primera cadena de dígitos como se mencionó anteriormente.

Si tiene una situación más complicada, puede utilizar la biblioteca 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
fuente
Además, solo una pregunta curiosa: ¿por qué usas en (t/refer-tupelo)lugar de hacer que el usuario lo haga (:require [tupelo.core :refer :all])?
Qwerp-Derp
refer-tupelosigue el modelo refer-clojure, ya que no incluye todo lo que (:require [tupelo.core :refer :all])hace.
Alan Thompson