Какой самый простой способ разбора чисел в clojure?
Я использую java для разбора чисел, например.
(. Integer parseInt numberString)
Есть ли более clojuriffic способ, который обрабатывал бы как целые числа, так и float, и возвращал числа clojure? Меня не особенно беспокоит производительность здесь, я просто хочу обработать кучу пробелов с пробелами в файле и сделать что-то с ними самым простым способом.
Таким образом, файл может иметь такие строки, как:
5 10 0.0002
4 12 0.003
И я хотел бы иметь возможность преобразовывать строки в векторы чисел.
Ответы
Ответ 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