Подтвердить что ты не робот

Какой самый простой способ разбора чисел в clojure?

Я использую java для разбора чисел, например.

(. Integer parseInt  numberString)

Есть ли более clojuriffic способ, который обрабатывал бы как целые числа, так и float, и возвращал числа clojure? Меня не особенно беспокоит производительность здесь, я просто хочу обработать кучу пробелов с пробелами в файле и сделать что-то с ними самым простым способом.

Таким образом, файл может иметь такие строки, как:

5  10  0.0002
4  12  0.003

И я хотел бы иметь возможность преобразовывать строки в векторы чисел.

4b9b3361

Ответ 1

Вы можете использовать Edn Reader для разбора чисел. Это имеет то преимущество, что дает вам поплавки или Bignums, когда это необходимо.

user> (require '[clojure.edn :as edn])
nil
user> (edn/read-string "0.002")
0.0020

Если вам нужен один огромный вектор чисел, вы можете обмануть и сделать это:

user> (let [input "5  10  0.002\n4  12  0.003"]
        (read-string (str "[" input "]")))
[5 10 0.0020 4 12 0.0030]

Отчасти хакерский. Или там re-seq:

user> (let [input "5  10  0.002\n4  12  0.003"]
        (map read-string (re-seq #"[\d.]+" input)))
(5 10 0.0020 4 12 0.0030)

Или один вектор на строку:

user> (let [input "5  10  0.002\n4  12  0.003"]
        (for [line (line-seq (java.io.BufferedReader.
                              (java.io.StringReader. input)))]
             (vec (map read-string (re-seq #"[\d.]+" line)))))
([5 10 0.0020] [4 12 0.0030])

Я уверен, что есть другие способы.

Ответ 2

Не уверен, что это "самый простой способ", но я подумал, что это было весело, поэтому... С помощью взлома отражения вы можете получить доступ только к части, читающей число Clojure Reader:

(let [m (.getDeclaredMethod clojure.lang.LispReader
                            "matchNumber"
                            (into-array [String]))]
  (.setAccessible m true)
  (defn parse-number [s]
    (.invoke m clojure.lang.LispReader (into-array [s]))))

Затем используйте так:

user> (parse-number "123")
123
user> (parse-number "123.5")
123.5
user> (parse-number "123/2")
123/2
user> (class (parse-number "123"))
java.lang.Integer
user> (class (parse-number "123.5"))
java.lang.Double
user> (class (parse-number "123/2"))
clojure.lang.Ratio
user> (class (parse-number "123123451451245"))
java.lang.Long
user> (class (parse-number "123123451451245123514236146"))
java.math.BigInteger
user> (parse-number "0x12312345145124")
5120577133367588
user> (parse-number "12312345142as36146") ; note the "as" in the middle
nil

Обратите внимание, что это не бросает обычный NumberFormatException, если что-то пойдет не так; вы можете добавить чек на nil и бросить его самостоятельно, если хотите.

Что касается производительности, позвольте иметь ненаучный микрообъект (обе функции были "разогреты", начальные прогоны были медленнее, как обычно):

user> (time (dotimes [_ 10000] (parse-number "1234123512435")))
"Elapsed time: 564.58196 msecs"
nil
user> (time (dotimes [_ 10000] (read-string "1234123512435")))
"Elapsed time: 561.425967 msecs"
nil

Очевидный отказ от ответственности: clojure.lang.LispReader.matchNumber является частным статическим методом clojure.lang.LispReader и может быть изменен или удален в любое время.

Ответ 3

Если вы хотите быть более безопасным, вы можете использовать Float/parseFloat

user=> (map #(Float/parseFloat (% 0)) (re-seq #"\d+(\.\d+)?" "1 2.2 3.5"))
(1.0 2.2 3.5)
user=> 

Ответ 4

На мой взгляд, лучший/безопасный способ, который работает, когда вы хотите его для любого числа, и терпит неудачу, когда он не является числом:

(defn parse-number
  "Reads a number from a string. Returns nil if not a number."
  [s]
  (if (re-find #"^-?\d+\.?\d*$" s)
    (read-string s)))

например.

(parse-number "43") ;=> 43
(parse-number "72.02") ;=> 72.02
(parse-number "009.0008") ;=> 9.008
(parse-number "-92837482734982347.00789") ;=> -9.2837482734982352E16
(parse-number "89blah") ;=> nil
(parse-number "z29") ;=> nil
(parse-number "(exploit-me)") ;=> nil

Работает для ints, floats/doubles, bignums и т.д. Если вы хотите добавить поддержку для чтения других обозначений, просто добавьте регулярное выражение.

Ответ 5

Предлагаемый подход Брайана Карпера (с использованием строки чтения) работает хорошо, но только до тех пор, пока вы не попытаетесь разобрать нулевые цифры, такие как "010". Обратите внимание:

user=> (read-string "010")
8
user=> (read-string "090")
java.lang.RuntimeException: java.lang.NumberFormatException: Invalid number: 090 (NO_SOURCE_FILE:0)

Это связано с тем, что clojure пытается проанализировать "090" как восьмеричную, а 090 не является допустимым восьмеричным!

Ответ 6

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

(ns edn-example.core
    (require [clojure.edn :as edn]))

(edn/read-string "2.7"); float 2.7
(edn/read-string "2"); int 2

просто, легко и безопасно;)

Ответ 7

Я считаю, что решение solussd отлично подходит для моего кода. Исходя из этого, здесь улучшается поддержка научной нотации. Кроме того, добавляется (.trim s), чтобы можно было переносить дополнительное пространство.

(defn parse-number
  "Reads a number from a string. Returns nil if not a number."
  [s]
  (if (re-find #"^-?\d+\.?\d*([Ee]\+\d+|[Ee]-\d+|[Ee]\d+)?$" (.trim s))
    (read-string s)))

например.

(parse-number "  4.841192E-002  ")    ;=> 0.04841192
(parse-number "  4.841192e2 ")    ;=> 484.1192
(parse-number "  4.841192E+003 ")    ;=> 4841.192
(parse-number "  4.841192e.2 ")  ;=> nil
(parse-number "  4.841192E ")  ;=> nil

Ответ 8

Используйте bigint и bigdec

(bigint "1")
(bigint "010") ; returns 10N as expected
(bigint "111111111111111111111111111111111111111111111111111")
(bigdec "11111.000000000000000000000000000000000000000000001")

Clojure bigint будет использовать примитивы, когда это возможно, избегая регулярных выражений, проблемы с восьмеричными литералами или ограниченным размером других числовых типов, вызывая (Integer. "10000000000").

(Последнее произошло со мной, и это было довольно запутанно: я завернул его в функцию parse-int, а потом просто предположил, что parse-int означает "разобрать натуральное целое", а не "анализировать 32-битное целое" )

Ответ 9

Это два лучших и правильных подхода:

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