Як можна перетворити рядок у Clojure в число?


128

У мене є різні рядки, деякі на кшталт "45", деякі - "45px". Як я конвертую обидва ці числа на число 45?


32
Я радий, що хтось не боїться задавати якісь основні питання.
восьминоги граббус

4
+1 - частина проблеми полягає в тому, що документи Clojure іноді не звертаються до цих "основних" питань, які ми вважаємо належними іншими мовами. (У мене було те саме питання через 3 роки, і я знайшов це).
Гленн

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

Нова відповідь

Мені подобається відповідь snrobot краще. Використання методу 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повинні бути такою, whenоскільки у ваших FNS немає іншого блоку.
quux00

1
Так, будь ласка, не припиняйте читати після першого чи другого фрагмента коду!
Бенджамін Аткін

2
Підказка на номери з провідними нулями. read-stringінтерпретує їх як восьмеричний: (read-string "08")кидає виняток. Integer/valueOfтрактує їх як десятковий: (Integer/valueOf "08")оцінює до 8.
рубасов

Зауважте також, що read-stringвикидаєте виняток, якщо даєте порожній рядок або щось на зразок "29px"
Ілля Бояндін

Як слід. Я відповів на запитання в заголовку і що люди очікують, побачивши цю сторінку, перш ніж я відповів на питання в тілі запитань. Це останній фрагмент коду в моїй відповіді.
Бенджамін Аткін

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

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

Дякую. Це було корисно в моєму розбитті продукту на послідовність цифр.
восьминоги граббус

3
Оскільки для цієї відповіді ми знаходимося в землі Java, то це, як правило, доцільно використовувати Integer/valueOf, а не конструктор 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функцію.
тар

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)))

Ця версія повертає нуль, якщо у введенні немає цифр, а не збільшення винятку.

Моє запитання - чи прийнятно скорочувати назву до "str-> int", чи такі речі завжди повинні бути повністю уточнені.


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" не дозволено), і дозволити абоненту обробляти не цифри, повертаючи нуль:

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

І якщо Floats є проблематичними для вашого домену замість Float/parseFloatput bigdecабо чогось іншого.


3

Для всіх, хто хоче розібрати більш звичайний лінійний рядок на число, тобто рядок, який не має інших символів, що не є числовими. Це два найкращі підходи:

Використання Java interop:

(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


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-Derp

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