Как в Clojure преобразовать строку в число?


130

У меня есть разные строки, некоторые вроде «45», некоторые вроде «45 пикселей». Как мне преобразовать их в число 45?


33
Я рад, что кто-то не боится задать элементарные вопросы.
octopusgrabbus

4
+1 - часть проблемы заключается в том, что документация Clojure иногда не отвечает на эти «базовые» вопросы, которые мы принимаем как должное в других языках. (У меня был тот же вопрос 3 года спустя, и я нашел это).
Glenn

3
@octopusgrabbus - мне было бы интересно узнать, «почему» люди боятся задавать простые вопросы?
appshare.co

1
@Zubair, предполагается, что основные вещи уже где-то объяснены, поэтому вы, скорее всего, что-то упустили, и ваш вопрос будет отклонен из-за отсутствия исследовательских усилий.
Ал.Г.

1
Для тех , кто прибывает сюда из Google ищет , чтобы преобразовать "9"в 9это самое лучшее , что работал для меня (Integer. "9").
weltschmerz

Ответы:


79

Это будет работать на 10pxилиpx10

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

он будет анализировать первую непрерывную цифру только так

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

Хороший ответ! На мой взгляд, это лучше, чем использование строки чтения. Я изменил свой ответ, чтобы использовать вашу технику. Я также внес несколько небольших изменений.
Бенджамин Аткин

это дает мнеException in thread "main" java.lang.ClassNotFoundException: Integer.,
maazza

83

Новый ответ

Мне больше нравится ответ снроботов. В этом простом случае использование метода Java проще и надежнее, чем использование строки чтения. Я сделал пару небольших изменений. Поскольку автор не исключил отрицательные числа, я скорректировал его, чтобы разрешить отрицательные числа. Я также сделал так, чтобы число начиналось с начала строки.

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

Кроме того, я обнаружил, что Integer / parseInt анализирует как десятичное, когда не задано основание, даже если есть ведущие нули.

Старый ответ

Во-первых, чтобы проанализировать только целое число (поскольку это хит в Google и хорошая справочная информация):

Вы можете использовать ридер :

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

Вы можете проверить, что это номер после прочтения:

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

Я не уверен, можно ли доверять пользовательскому вводу читателю clojure, чтобы вы могли проверить его перед чтением:

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

Думаю, я предпочитаю последнее решение.

А теперь к вашему конкретному вопросу. Чтобы проанализировать что-то, что начинается с целого числа, например 29px:

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

Мне больше всего нравится ваш ответ - жаль, что это не предусмотрено базовой библиотекой clojure. Одна небольшая критика - технически вы ifдолжны быть a, whenпоскольку в ваших fns нет блока else.
quux00

1
Да, пожалуйста, не прекращайте читать после первого или второго фрагмента кода!
Бенджамин Аткин,

2
Хедз-ап на числах с ведущими нулями. read-stringинтерпретирует их как восьмеричные: (read-string "08")выдает исключение. Integer/valueOfобрабатывает их как десятичные: (Integer/valueOf "08")оценивает до 8.
rubasov 05

Также обратите внимание, что read-stringвыдает исключение, если вы указываете ему пустую строку или что-то вроде «29px»
Илья Бояндин

Как это должно. Я ответил на вопрос в заголовке и что люди ожидают, когда увидят эту страницу, прежде чем я ответил на вопрос в теле вопроса. Это последний фрагмент кода в моем ответе.
Бенджамин Аткин

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

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

Спасибо. Это помогло мне разбить продукт на последовательность цифр.
octopusgrabbus

3
Поскольку для этого ответа мы находимся в стране Java, обычно рекомендуется использовать Integer/valueOfконструктор Integer, а не Integer. Класс Integer кэширует значения от -128 до 127, чтобы минимизировать создание объекта. Документ Integer Javadoc описывает это так же, как и этот пост: stackoverflow.com/a/2974852/871012
quux00

15

Для меня это работает в ответе, намного проще.

(строка чтения "123")

=> 123


1
Будьте осторожны, используя это с пользовательским вводом. read-stringможет выполнять код в соответствии с документами: clojuredocs.org/clojure.core/read-string
jerney

это отлично подходит для надежного ввода, например, для задач программирования. @jerney прав: будьте осторожны, чтобы не использовать его в реальном коде.
hraban

10

AFAIK нет стандартного решения вашей проблемы. Я думаю, что что-то вроде следующего, который использует clojure.contrib.str-utils2/replace, должно помочь:

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

Не рекомендуется. Он будет работать до тех пор, пока кто-нибудь его не бросит 1.5... и он также не использует встроенную clojure.string/replaceфункцию.
tar

8

Это не идеально, но вот что - то с filter, Character/isDigitи Integer/parseInt. Он не будет работать для чисел с плавающей запятой и не работает, если во входных данных нет цифры, поэтому вам, вероятно, следует очистить его. Я надеюсь, что есть более приятный способ сделать это, не использующий столько 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

Я бы, наверное, добавил к требованиям несколько вещей:

  • Должен начинаться с цифры
  • Должен терпеть пустые входы
  • Допускает передачу любого объекта (стандарт toString)

Может быть что-то вроде:

(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

а затем, возможно, бонусные баллы за то, чтобы сделать это мульти-методом, который допускает пользовательское значение по умолчанию, отличное от 0.


4

Расширяя ответ snrobot:

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

Эта версия возвращает nil, если во входных данных нет цифр, а не вызывает исключение.

У меня вопрос, допустимо ли сокращать имя до «str-> int» или такие вещи всегда должны быть полностью указаны.


4

Для всех, кто хочет преобразовать более обычный строковый литерал в число, то есть строку, в которой нет других нечисловых символов. Это два лучших подхода:

Использование взаимодействия с 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")

Это позволяет вам точно контролировать тип, в котором вы хотите проанализировать число, когда это важно для вашего варианта использования.

Используя программу чтения Clojure EDN:

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

В отличие от использования, read-stringиз clojure.coreкоторого небезопасно использовать для ненадежного ввода, edn/read-stringбезопасно запускать для ненадежного ввода, такого как пользовательский ввод.

Это часто более удобно, чем взаимодействие с Java, если вам не нужно иметь особый контроль над типами. Он может анализировать любой числовой литерал, который может анализировать Clojure, например:

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

Полный список здесь: https://www.rubberducking.com/2019/05/clojure-for-non-clojure-programmers.html#numbers


3

Также с помощью (re-seq)функции можно расширить возвращаемое значение до строки, содержащей все числа, существующие во входной строке, в следующем порядке:

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

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

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


3

Вопрос касается разбора строки на число.

(number? 0.5)
;;=> true

Таким образом, из приведенных выше десятичных знаков также следует анализировать.

Возможно, сейчас не совсем отвечаю на вопрос, но для общего использования, я думаю, вы хотели бы быть строгими в отношении того, является ли это число или нет (поэтому «px» не разрешено), и позволить вызывающей стороне обрабатывать не числа, возвращая nil:

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

И если для вашего домена проблематичны поплавки, а не Float/parseFloatпоставить bigdecили еще что-то.


2

В простых случаях вы можете просто использовать регулярное выражение, чтобы вытащить первую строку цифр, как указано выше.

Если у вас более сложная ситуация, вы можете использовать библиотеку 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)а не заставляете пользователя делать (:require [tupelo.core :refer :all])?
Qwerp-

refer-tupeloсмоделирован после того refer-clojure, как он не включает в себя все, что (:require [tupelo.core :refer :all])делает способ .
Алан Томпсон,
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.