在 Clojure 中,如何将字符串转换为数字?

Posted

技术标签:

【中文标题】在 Clojure 中,如何将字符串转换为数字?【英文标题】:In Clojure how can I convert a String to a number? 【发布时间】:2011-08-03 00:55:31 【问题描述】:

我有各种字符串,有些像“45”,有些像“45px”。如何将这两个转换为数字 45?

【问题讨论】:

我很高兴有人不怕问一些基本问题。 +1 - 部分挑战在于 Clojure 文档有时无法解决我们在其他语言中认为理所当然的这些“基本”问题。 (3 年后我有同样的问题并发现了这个)。 @octopusgrabbus - 我很想知道“为什么”人们害怕问基本问题? @Zubair 应该已经在某个地方解释了基本的东西,所以你很可能忽略了一些东西,你的问题会因为“没有研究工作”而被否决。 对于那些从 Google 来到这里并希望将 "9" 转换为 9 的人,这是对我最有效的方法:(Integer. "9") 【参考方案1】:

这将适用于 10pxpx10

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

它只会解析第一个连续数字

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

【讨论】:

不错的答案!在我看来,这比使用 read-string 更好。我更改了答案以使用您的技术。我也做了一些小改动。 这给了我Exception in thread "main" java.lang.ClassNotFoundException: Integer., 【参考方案2】:

新答案

我更喜欢 snrobot 的回答。对于这个简单的用例,使用 Java 方法比使用 read-string 更简单、更健壮。我确实做了一些小改动。由于作者没有排除负数,所以我将其调整为允许负数。我也做了,所以它要求数字从字符串的开头开始。

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

此外,我发现 Integer/parseInt 在没有给出基数时解析为十进制,即使有前导零。

旧答案

首先,只解析一个整数(因为这在谷歌上很受欢迎,而且它是很好的背景信息):

你可以使用reader:

(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 中没有 else 块。 是的,请不要在第一个或第二个代码sn-p之后停止阅读! 前导零的数字提示。 read-string 将它们解释为八进制:(read-string "08") 抛出异常。 Integer/valueOf 将它们视为十进制:(Integer/valueOf "08") 计算结果为 8。 还要注意read-string如果你给它一个空字符串或类似“29px”的东西会抛出一个异常 应该如此。在我回答问题正文中的问题之前,我回答了标题中的问题,以及人们在看到此页面时的期望。这是我答案正文中的最后一个代码 sn-p。【参考方案3】:
(defn parse-int [s]
  (Integer. (re-find #"[0-9]*" s)))

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

【讨论】:

谢谢。这有助于我将产品拆分为一系列数字。 由于我们在 Java 领域寻求这个答案,因此通常建议使用 Integer/valueOf,而不是 Integer 构造函数。 Integer 类缓存 -128 到 127 之间的值以最小化对象创建。 Integer Javadoc 对此进行了如下描述:***.com/a/2974852/871012【参考方案4】:

这对我来说适用于 repl,更直接。

(读取字符串“123”)

=> 123

【讨论】:

在用户输入中使用它时要小心。 read-string 可以按照文档执行代码:clojuredocs.org/clojure.core/read-string 这对于受信任的输入非常有用,例如一个编程难题。 @jerney 是对的:注意不要在实际代码中使用它。【参考方案5】:

AFAIK 没有针对您的问题的标准解决方案。我认为以下使用clojure.contrib.str-utils2/replace 的内容应该有所帮助:

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

【讨论】:

不推荐。它会一直工作,直到有人向它抛出 1.5...而且它也没有使用内置的 clojure.string/replace 函数。【参考方案6】:

这并不完美,但这里有 filterCharacter/isDigitInteger/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)

【讨论】:

【参考方案7】:

我可能会在要求中添加一些内容:

必须以数字开头 必须容忍空输入 允许传递任何对象(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 以外的默认值。

【讨论】:

【参考方案8】:

扩展 snrobot 的答案:

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

如果输入中没有数字,则此版本返回 nil,而不是引发异常。

我的问题是是否可以将名称缩写为“str->int”,或者是否应该始终完全指定此类内容。

【讨论】:

【参考方案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")

与使用clojure.core 中的read-string 不同,在不受信任的输入上使用是不安全的,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

【讨论】:

【参考方案10】:

同样使用(re-seq)函数可以将返回值扩展为包含输入字符串中存在的所有数字的字符串:

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

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

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

【讨论】:

【参考方案11】:

问题是关于将字符串解析为数字。

(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 或其他内容。

【讨论】:

【参考方案12】:

对于简单的情况,您可以使用正则表达式来提取上面提到的第一个数字字符串。

如果您有更复杂的情况,您可能希望使用 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]) refer-tupelo 仿照refer-clojure,因为它不像(:require [tupelo.core :refer :all]) 那样包含所有内容。【参考方案13】:

如何避免某些字符串出现异常?

(defn string-to-number [in]
  (let [s (strip-whitespace in)      ;; trim
        f (re-find #"\d+" s)]        ;; search digit else nil
    (if f (Integer/parseInt f) 0)))  ;; if not-nil do cast

(string-to-number "-")
(string-to-number "10")
(string-to-number "px10")
(string-to-number "1200 xr")

【讨论】:

以上是关于在 Clojure 中,如何将字符串转换为数字?的主要内容,如果未能解决你的问题,请参考以下文章

如何在Clojure中将字符强制转换为int?

如何在 Clojure 中将字符的 LazySeq 转换为字符串?

将“数字”字符串格式化为数字

Clojure:如何将映射条目的惰性序列转换为结构映射?

PHP如何将从二进制文件中读取的字节转换为数字

在sqlserver中怎么将一个时间字串值转换成时间戳的数字形式