Какой самый простой способ разобрать числа в clojure? - PullRequest
50 голосов
/ 14 апреля 2010

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

(. Integer parseInt  numberString)

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

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

5  10  0.0002
4  12  0.003

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

Ответы [ 9 ]

59 голосов
/ 14 апреля 2010

Вы можете использовать считыватель edn для разбора чисел. Это также дает вам преимущество, если вы тоже хотите использовать поплавки или 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])

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

25 голосов
/ 16 апреля 2010

Не уверен, что это "самый простой способ", но я подумал, что это довольно забавно, так что ... С помощью взлома отражения вы можете получить доступ только к части чтения чисел в 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 и может быть изменен или удален в любое время.

22 голосов
/ 15 апреля 2010

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

user=> (map #(Float/parseFloat (% 0)) (re-seq #"\d+(\.\d+)?" "1 2.2 3.5"))
(1.0 2.2 3.5)
user=> 
19 голосов
/ 05 сентября 2012

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

(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

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

15 голосов
/ 14 апреля 2011

Предложенный подход Брайана Карпера (с использованием read-string) работает хорошо, но только до тех пор, пока вы не попытаетесь разобрать числа с нулем, такие как "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 не является действительным восьмеричным!

14 голосов
/ 28 апреля 2016

Ответ Брайана Карпера почти правильный. Вместо использования read-string непосредственно из ядра 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 голосов
/ 11 апреля 2014

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

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

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

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

7 голосов
/ 21 января 2013

Я считаю, что ответ 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
0 голосов
/ 11 июня 2019

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...