No Clojure, como posso converter uma String em um número?

128

Eu tenho várias strings, algumas como "45", outras como "45px". Como eu converto ambos para o número 45?

appshare.co
fonte
32
Fico feliz que alguém não tenha medo de fazer algumas perguntas básicas.
Octopusgrabbus
4
+1 - parte do desafio é que os documentos do Clojure às vezes não abordam essas questões "básicas" que consideramos óbvias em outros idiomas. (Eu tive a mesma pergunta três anos depois e encontrei isso).
Glenn
3
@octopusgrabbus - Gostaria de saber "por que" as pessoas têm medo de fazer perguntas básicas?
appshare.co 26/01
1
@Zubair, supõe-se que as coisas básicas já foram explicadas em algum lugar, então você provavelmente ignorou algo e sua pergunta será rebaixada por "nenhum esforço de pesquisa".
Al.G.
1
Para quem vem aqui do Google olhando para converter "9"em 9, esta é a melhor coisa que funcionou para mim: (Integer. "9").
weltschmerz

Respostas:

79

Isso funcionará em 10px oupx10

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

ele analisará o primeiro dígito contínuo apenas para

user=> (parse-int "10not123")
10
user=> (parse-int "abc10def11")
10
jhnstn
fonte
Boa resposta! Isso é melhor do que usar read-string na minha opinião. Mudei minha resposta para usar sua técnica. Também fiz algumas pequenas alterações.
Benjamin Atkin
isso me dáException in thread "main" java.lang.ClassNotFoundException: Integer.,
maazza 31/03
83

Nova resposta

Eu gosto mais da resposta do snrobot. O uso do método Java é mais simples e mais robusto do que o uso de string de leitura para este caso de uso simples. Fiz algumas pequenas alterações. Como o autor não descartou números negativos, ajustei-o para permitir números negativos. Eu também fiz isso, requer que o número comece no início da string.

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

Além disso, descobri que Integer / parseInt analisa como decimal quando nenhuma raiz é fornecida, mesmo se houver zeros à esquerda.

Resposta antiga

Primeiro, para analisar apenas um número inteiro (já que este é um sucesso no google e é uma boa informação de plano de fundo):

Você pode usar o leitor :

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

Você pode verificar se é um número depois de ler:

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

Não tenho certeza se a entrada do usuário pode ser confiável pelo leitor de clojure para que você possa verificar antes de ler também:

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

Eu acho que prefiro a última solução.

E agora, para sua pergunta específica. Para analisar algo que começa com um número inteiro, como 29px:

(read-string (second (re-matches (re-pattern "(\\d+).*") "29px"))) ; => 29
Benjamin Atkin
fonte
Eu gosto mais da sua resposta - pena que isso não seja fornecido pela biblioteca principal do clojure. Uma crítica menor - tecnicamente, você ifdeve ser uma, whenjá que não há mais nenhum bloqueio no seu DNS.
quux00
1
Sim, por favor, não pare de ler após o primeiro ou o segundo trecho de código!
Benjamin Atkin 22/03
2
Um alerta sobre números com zeros à esquerda. read-stringinterpreta-os como octal: (read-string "08")lança uma exceção. Integer/valueOftrata-los como decimal: (Integer/valueOf "08")avalia a 8.
rubasov
Note também que read-stringlança uma exceção se você dar-lhe uma cadeia vazia ou algo como "29px"
Ilya Boyandin
Como deveria. Respondi à pergunta no título e o que as pessoas esperam quando vêem esta página, antes de responder à pergunta no corpo da pergunta. É o último trecho de código no corpo da minha resposta.
Benjamin Atkin
30
(defn parse-int [s]
  (Integer. (re-find #"[0-9]*" s)))

user> (parse-int "10px")
10
user> (parse-int "10")
10
MayDaniel
fonte
Obrigado. Isso foi útil para dividir um produto em uma sequência de dígitos.
Octopusgrabbus
3
Como estamos na área de Java para esta resposta, geralmente é aconselhável usar Integer/valueOf, em vez do construtor Integer. A classe Integer armazena em cache valores entre -128 e 127 para minimizar a criação do objeto. O Javadoc inteiro descreve isso da mesma forma que esta postagem: stackoverflow.com/a/2974852/871012
quux00
15

Isso funciona em substituição a mim, muito mais direto.

(sequência de leitura "123")

=> 123

f3rz_net
fonte
1
Cuidado ao usar isso com a entrada do usuário. read-stringpode executar código por docs: clojuredocs.org/clojure.core/read-string
Jerney
isso é ótimo para informações confiáveis, como, por exemplo, um quebra-cabeça de programação. @ jerney está certo: tenha cuidado para não usá-lo no código real.
Hraban
10

AFAIK não há solução padrão para o seu problema. Eu acho que algo como o seguinte, que usa clojure.contrib.str-utils2/replace, deve ajudar:

(defn str2int [txt]
  (Integer/parseInt (replace txt #"[a-zA-Z]" "")))
skuro
fonte
Não recomendado. Funcionará até que alguém o jogue 1.5... e também não faz uso da clojure.string/replacefunção interna.
tar
8

Isso não é perfeito, mas aqui está algo com filter, Character/isDigite Integer/parseInt. Ele não funcionará para números de ponto flutuante e falhará se não houver dígito na entrada; portanto, você provavelmente deve limpá-lo. Espero que haja uma maneira melhor de fazer isso que não envolva muito 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
fonte
4

Eu provavelmente adicionaria algumas coisas aos requisitos:

  • Tem que começar com um dígito
  • Tem que tolerar entradas vazias
  • Tolera a aprovação de qualquer objeto (toString é padrão)

Talvez 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

e então talvez pontos de bônus por tornar esse um método múltiplo que permita um padrão fornecido pelo usuário diferente de 0.

Tony K.
fonte
4

Expandindo a resposta do snrobot:

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

Esta versão retorna nulo se não houver dígitos na entrada, em vez de gerar uma exceção.

Minha pergunta é se é aceitável abreviar o nome para "str-> int" ou se coisas assim devem sempre ser totalmente especificadas.

Nick Vargish
fonte
3

Também o uso da (re-seq)função pode estender o valor de retorno para uma sequência que contém todos os números existentes na sequência de entrada na ordem:

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

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

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


fonte
3

A pergunta é feita sobre a análise de uma string em um número.

(number? 0.5)
;;=> true

Portanto, os decimais acima também devem ser analisados.

Talvez não esteja exatamente respondendo à pergunta agora, mas, para uso geral, acho que você gostaria de ser rigoroso sobre se é um número ou não (portanto, "px" não é permitido) e deixar que o chamador lide com não-números retornando zero:

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

E se os flutuadores forem problemáticos para o seu domínio, em vez de Float/parseFloatcolocar bigdecou algo mais.

Chris Murphy
fonte
3

Para quem quer analisar um literal String mais normal em um número, ou seja, uma string que não possui outros caracteres não numéricos. Estas são as duas melhores abordagens:

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

Isso permite controlar com precisão o tipo em que você deseja analisar o número, quando isso for importante para o seu caso de uso.

Usando o leitor Clojure EDN:

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

Ao contrário do uso read-stringdo clojure.corequal não é seguro usar em entradas não confiáveis, edn/read-stringé seguro executar em entradas não confiáveis, como a entrada do usuário.

Isso geralmente é mais conveniente do que a interoperabilidade Java, se você não precisar ter controle específico dos tipos. Ele pode analisar qualquer número literal que o Clojure possa analisar, como:

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

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

Didier A.
fonte
2

Para casos simples, você pode usar apenas uma regex para extrair a primeira sequência de dígitos, como mencionado acima.

Se você tiver uma situação mais complicada, poderá usar a 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
fonte
Além disso, apenas uma pergunta curiosa: por que você usa em (t/refer-tupelo)vez de fazer o usuário fazer (:require [tupelo.core :refer :all])?
Qwerp-Derp 24/10
refer-tupeloé modelado depois refer-clojure, na medida em que não inclui tudo do jeito que (:require [tupelo.core :refer :all])faz.
27617 Alan Thompson