Skip to content

Instantly share code, notes, and snippets.

@d11wtq
Last active August 29, 2015 14:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save d11wtq/bc66b49efe7c7f2df875 to your computer and use it in GitHub Desktop.
Save d11wtq/bc66b49efe7c7f2df875 to your computer and use it in GitHub Desktop.
Writing int->str (Clojure)

int->str

This function computes the English string for any given integer supported by Clojure.

Example

(int->str 110917) ; "one hundred and ten thousand nine hundred and seventeen"

How it works

Components in a number

The most important trick used is to first break the number down into its components (i.e. its millions, its thousands, hundreds and ones). We do this by successively dividing the number by the size of each component, starting with the largest supported component and carrying over the remainder. The function solve-components takes care of this and outputs something of the form:

(solve-components 110927) ;[[:thousand 110] [:hundred 9] [:ten 2] [:one 7]]

Conversion to a string

Now we can write a function to name each component pair. The function str-of-pair does this.

(str-of-pair [:thousand 110]) ; "one hundred and ten thousand"

For everything except the :tens, :ones and :teens this is just a case of saying (str (int->str value) " " (name component)). Note the recursion back into int->str here in order to avoid saying "110 thousand". For the special cases, we use a lookup table.

Dealing with teens

There is a special case in English, where [:ten 1] [:one 7] isn't "ten seven" but is rather "seventeen". We handle this by eliminating the :ten and the :one components and replacing them with a :teen component (e.g. [:teen 7]). The string conversion has a lookup table for :teen. The teenify function handle the substitution and is performed at the end of solve-components.

Dealing with hundreds

In English, when a number is not a round hundred, we use the conjuction "and" before the remaining number. To handle this, we rebuild our list of components in reverse. If we see a :hundred and we've already processed something before it, we inject the [:str "and"] component, otherwise we just copy as-is. Remember we're processing in reverse to simplify the need to lookahead. The function andify does this substitution and is performed at the end of solve-components. Our str-of-pair treats :str components as verbatim pieces of text.

(ns numbers.core
(require [clojure.string :as string]))
(declare int->str)
(def sizes
"Table of numeric units and sizes."
[[:trillion 1000000000000]
[:billion 1000000000]
[:million 1000000]
[:thousand 1000]
[:hundred 100]
[:ten 10]
[:one 1]])
(def names
"Table of numeric units and names"
{:teen ["eleven" "twelve" "thirteen" "fourteen" "fifteen"
"sixteen" "seventeen" "eighteen" "nineteen"]
:ten ["ten" "twenty" "thirty" "forty" "fifty"
"sixty" "seventy" "eighty" "ninety"]
:one ["one" "two" "three" "four" "five"
"six" "seven" "eight" "nine"]})
(defn divide
"Performs: x/y = [z,r] where r is the remainder."
[x y]
(let [remainder (mod x y)]
[(/ (- x remainder) y) remainder]))
(defn teenify
"Substitutes [:ten 1] [:one n] in components for [:teen n]."
[components]
(let [[[one x] [ten y] & more] (reverse components)]
(if (and (= :ten ten) (= 1 y))
(reverse (into more [[:teen x]]))
components)))
(defn andify
"Substitutes [:hundred n] ... for [:hundred n] [:str \"and\"] ..."
[components]
(reduce (fn [acc [unit n :as pair]]
(into acc
(or (if (and (not (empty? acc))
(= :hundred unit))
[[:str "and"] pair])
[pair])))
(list)
(reverse components)))
(defn solve-components
"Convert n into a table of its component units."
[n]
(letfn [(accumulate-units [[n units] [tag size]]
(let [[quantity remainder] (divide n size)]
[remainder (conj units [tag quantity])]))]
(->> (reduce accumulate-units [n (vector)] sizes)
(last)
(filter (fn [[tag n]] (> n 0)))
(teenify)
(andify))))
(defn str-of-pair
"String value of the individual component pair."
[[component v]]
(if-let [lookup (names component)]
(lookup (dec v))
(case component
:str v
(str (int->str v) " " (name component)))))
(defn int->str
"Convert integer n into the English string."
[n]
(string/join " " (map str-of-pair (solve-components n))))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment