I have various strings, some like \"45\", some like \"45px\". How how I convert both of these to the number 45?
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"))))
This works in repl for me, much more straight forward.
(read-string "123")
=> 123
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]" "")))
I would probably add a few things to the requirements:
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.
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
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