In Clojure how can I convert a String to a number?

Clojure

Clojure Problem Overview


I have various strings, some like "45", some like "45px". How how I convert both of these to the number 45?

Clojure Solutions


Solution 1 - Clojure

This will work on 10px or px10

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

it will parse the first continuous digit only so

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

Solution 2 - Clojure

New answer

I like snrobot's answer better. Using the Java method is simpler and more robust than using read-string for this simple use case. I did make a couple of small changes. Since the author didn't rule out negative numbers, I adjusted it to allow negative numbers. I also made it so it requires the number to start at the beginning of the string.

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

Additionally I found that Integer/parseInt parses as decimal when no radix is given, even if there are leading zeroes.

Old answer

First, to parse just an integer (since this is a hit on google and it's good background information):

You could use the reader:

(read-string "9") ; => 9

You could check that it's a number after it's read:

(defn str->int [str] (if (number? (read-string str))))

I'm not sure if user input can be trusted by the clojure reader so you could check before it's read as well:

(defn str->int [str] (if (re-matches (re-pattern "\\d+") str) (read-string str)))

I think I prefer the last solution.

And now, to your specific question. To parse something that starts with an integer, like 29px:

(read-string (second (re-matches (re-pattern "(\\d+).*") "29px"))) ; => 29

Solution 3 - Clojure

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

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

Solution 4 - Clojure

This works in repl for me, much more straight forward.

(read-string "123")

=> 123

Solution 5 - Clojure

AFAIK there's no standard solution for your problem. I think something like the following, which uses clojure.contrib.str-utils2/replace, should help:

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

Solution 6 - Clojure

This isn't perfect, but here's something with filter, Character/isDigit and Integer/parseInt. It won't work for floating point numbers and it fails if there is no digit in the input, so you should probably clean it up. I hope there's a nicer way of doing this that doesn't involve so much 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)

Solution 7 - Clojure

I would probably add a few things to the requirements:

  • Has to start with a digit
  • Has to tolerate empty inputs
  • Tolerates being passed any object (toString is standard)

Maybe something like:

(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

and then perhaps bonus points for making this a multi-method that allows for a user-supplied default other than 0.

Solution 8 - Clojure

Expanding on snrobot's answer:

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

This versions returns nil if there are no digits in the input, rather than raising an exception.

My question is whether it's acceptable to abbreviate the name to "str->int", or if things like this should always be fully specified.

Solution 9 - Clojure

For anyone else looking to parse a more normal String literal into a number, that is, a string which doesn't have other non numeric characters. These are the two best approaches:

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

This lets you precisely control the type you want to parse the number in, when that matters to your use case.

Using the Clojure EDN reader:

(require '[clojure.edn :as edn])
(edn/read-string "333")

Unlike using read-string from clojure.core which isn't safe to use on untrusted input, edn/read-string is safe to run on untrusted input such as user input.

This is often more convenient then the Java interop if you don't need to have specific control of the types. It can parse any number literal that Clojure can parse such as:

;; Ratios
(edn/read-string "22/7")
;; Hexadecimal
(edn/read-string "0xff")

Full list here: https://www.rubberducking.com/2019/05/clojure-for-non-clojure-programmers.html#numbers

Solution 10 - Clojure

Also using (re-seq) function can extend the return value to a string containing all the numbers existing in the input string in order:

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

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

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

Solution 11 - Clojure

The question asks about parsing a string into a number.

(number? 0.5)
;;=> true

So from the above decimals ought to be parsed as well.

Perhaps not exactly answering the question now, but for general use I think you would want to be strict about whether it is a number or not (so "px" not allowed) and let the caller handle non-numbers by returning nil:

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

And if Floats are problematic for your domain instead of Float/parseFloat put bigdec or something else.

Solution 12 - Clojure

For simple cases you can just use a regex to pull out the first string of digits as mentioned above.

If you have a more complicated situation you may wish to use the InstaParse library:

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

Solution 13 - Clojure

How about this one to avoid an exception on certain strings ?

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionyazz.comView Question on Stackoverflow
Solution 1 - ClojurejhnstnView Answer on Stackoverflow
Solution 2 - ClojureBenjamin AtkinView Answer on Stackoverflow
Solution 3 - ClojureMayDanielView Answer on Stackoverflow
Solution 4 - Clojuref3rz_netView Answer on Stackoverflow
Solution 5 - ClojureskuroView Answer on Stackoverflow
Solution 6 - ClojuremichiakigView Answer on Stackoverflow
Solution 7 - ClojureTony K.View Answer on Stackoverflow
Solution 8 - ClojureNick VargishView Answer on Stackoverflow
Solution 9 - ClojureDidier A.View Answer on Stackoverflow
Solution 10 - Clojureuser4813927View Answer on Stackoverflow
Solution 11 - ClojureChris MurphyView Answer on Stackoverflow
Solution 12 - ClojureAlan ThompsonView Answer on Stackoverflow
Solution 13 - ClojureITSecMediaView Answer on Stackoverflow