Hey, dev.to community!
Today I want to share three different ways of converting a number (in the range of [0, 999,999,999,999]) to its English word form. For example, 34567 would become "thirty-four thousand five hundred sixty-seven" and 1001001 would become "one million one thousand one".
First Implementation: num-to-word-1
(:require [clojure.pprint :refer [cl-format]]
[clojure.string :as str])
(defn num-to-word-1 [n]
(if (<= 0 n 999999999999)
(str/replace
(cl-format nil "~R" n)
"," "")
(throw (IllegalArgumentException.))))
It uses a built-in function cl-format
which is a part of clojure.pprint
library. Although it is the most readable and easy to implement (out of the three implementations discussed in this article), it is also the slowest.
Second Implementation: num-to-word-2
(def ^:private ones {\0 "" \1 "one", \2 "two", \3 "three", \4 "four", \5 "five", \6 "six",
\7 "seven", \8 "eight", \9 "nine"})
(def ^:private tens {\0 "",
\1 {\0 "ten" \1 "eleven", \2 "twelve", \3 "thirteen", \4 "fourteen",
\5 "fifteen", \6 "sixteen", \7 "seventeen", \8 "eighteen", \9 "nineteen"},
\2 "twenty", \3 "thirty", \4 "forty", \5 "fifty",
\6 "sixty", \7 "seventy", \8 "eighty", \9 "ninety"})
(def ^:private hundreds {\0 "" \1 "one hundred", \2 "two hundred", \3 "three hundred", \4 "four hundred",
\5 "five hundred", \6 "six hundred", \7 "seven hundred", \8 "eight hundred", \9 "nine hundred"})
(defn- ones-tens [o t]
(if (= \1 t)
[((tens \1) o)]
(->> [(tens t) (ones o)]
(remove #(or (nil? %) (empty? %)))
(interpose "-")
(apply str)
vector)))
(defn- parse-item [item]
(let [label (keys item)
[o t h] (first (vals item))]
(if (= \0 o t h)
'()
(concat label (ones-tens o t) [(hundreds h)]))))
(defn num-to-word-2 [n]
(cond
(= 0 n) "zero"
(<= 1 n 999999999999)
(let [scale ["" "thousand" "million" "billion"]
nil-empty? #(or (nil? %) (empty? %))]
(->> n str reverse
(partition-all 3)
(map hash-map scale)
(reduce #(into %1 (parse-item %2)) '())
(remove nil-empty?)
(interpose " ")
(apply str)))
:else (throw (IllegalArgumentException. "Number out of range"))))
This implementation uses three separate maps of ones
, tens
, and hundreds
. The idea is to a number n
, convert it into a string, then break it into groups of three (partition-all 3)
, attach a scale to each group (map hash-map scale)
, iterate over the groups using reduce
converting each group into words using helper functions parse-item
and ones-tens
, then finally combine and convert the result back into a string to form the final output.
Third Implementation: num-to-word-3
(:require [clojure.string :as str])
(def ^:private lookup
{0 "zero" 1 "one" 2 "two" 3 "three" 4 "four"
5 "five" 6 "six" 7 "seven" 8 "eight" 9 "nine"
10 "ten" 11 "eleven" 12 "twelve" 13 "thirteen" 14 "fourteen"
15 "fifteen" 16 "sixteen" 17 "seventeen" 18 "eighteen"
19 "nineteen"
20 "twenty" 30 "thirty" 40 "forty"50 "fifty"
60 "sixty" 70 "seventy" 80 "eighty" 90 "ninety"})
(def ^:private scale
[[1000000000 "billion"]
[1000000 "million"]
[1000 "thousand"]
[100 "hundred"]])
(defn- to-word [n]
(cond (contains? lookup n) (lookup n)
(< n 100) (let [r (rem n 10)]
(str (to-word (- n r)) "-" (to-word r)))
:else
(let [[[limit label]] (drop-while (fn [[l]] (< n l)) scale)
[qs r] ((juxt (comp to-word quot) rem) n limit)
rs (when-not (zero? r) (to-word r))]
(str/trimr (str/join " " [qs label rs])))))
(defn num-to-word-3 [n]
(if (not (<= 0 n 999999999999))
(throw (IllegalArgumentException.))
(to-word n)))
This implementation uses a single lookup
map for the numbers 0-19 and {20 30 40 50 60 70 80 90} and a 2D vector scale
. The function makes use of recursion. If the number is in lookup
, the corresponding string is returned or else the function matches the number against scale
to determine a divisor, use this divisor to extract digits (quot
and rem
), and recalls itself for the extracted digits.
Comparison
Given below is the time taken by each of the three functions to convert 100,000 random numbers into words.
(time (dotimes [_ 100000]
(num-to-word-1 (rand-int 1000000000))))
;; "Elapsed time: 1448.9357 msecs"
;; nil
(time (dotimes [_ 100000]
(num-to-word-2 (rand-int 1000000000))))
;; "Elapsed time: 889.3392 msecs"
;; nil
(time (dotimes [_ 100000]
(num-to-word-3 (rand-int 1000000000))))
;; "Elapsed time: 374.0896 msecs"
;; nil
Keep exploring, experimenting and testing different options. Happy coding!
Top comments (0)