Skip to content

Instantly share code, notes, and snippets.

@joinr
Created June 26, 2019 20:41
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 joinr/53fa5566cfe1b097425b44786565befa to your computer and use it in GitHub Desktop.
Save joinr/53fa5566cfe1b097425b44786565befa to your computer and use it in GitHub Desktop.
a simple exploration of different ways to print monetarily formatted strings of numbers
(ns numberdemo
(:require [clojure.pprint :as pprint]))
;;from Craig
;;doesn't handle decimals.
(defn commarize
"given the string representation of a number, insert a comma after
every three digits."
[num-str]
;;reverse the string since the first comma starts from the right
;;side
(let [st (reverse num-str)]
(->>
;;reduce over the range so that we can test the digit position
(count st)
(range)
;;build up a list so that we are conjing to the front and the
;;digits are now back in order
(reduce (fn [acc c] (if (and (> c 0) (= (mod c 3) 0))
;;insert a comma every three digits, but we
;;don't need a comma after the first digit
;;when c=0 and (mod c 3) = 0
(conj acc (str (nth st c) ","))
(conj acc (nth st c)))) '())
;;turn our list back into a string
(reduce str))))
;;numberdemo> (time (dotimes [i 1000000] (commarize "1000000")))
;;"Elapsed time: 3455.465157 msecs"
;;this uses strings as sequences, not smart if you're
;;doing performance intensive stuff, but reads well.
;;doesn't handle decimals.
(defn commarize2 [s]
(->> (reverse s)
(partition-all 3)
(interpose ",")
flatten
reverse
clojure.string/join))
;;peforms slowly.
;;numberdemo> (time (dotimes [i 1000000] (commarize2 "1000000")))
;;"Elapsed time: 11301.111623 msecs"
;;using cl-format.
;;cl-format has plenty of printing directives, but they're type
;;specific. So if you want to format a number with commas added,
;;the input must be a number....
;;Dumb function to ensure our input is a number.
;;It'd be faster to parse specfically using Long, Double, Integer, etc.
;;but this is fine for demo.
(defn numericize [s]
(cond (number? s) s
(string? s) (let [n (clojure.edn/read-string s)]
(if (number? n)
n
(throw (ex-info "cannot coerce to number!" {:input s}))))
:else (throw (ex-info "cannot coerce to number!" {:input s}))))
;;now it's trivial.
;;handles decimals, performs slowly.
(defn commarize3 [s]
(pprint/cl-format nil "~:d" (numericize s)))
;;numberdemo> (time (dotimes [i 1000000] (commarize3 "1000000")))
;;"Elapsed time: 29351.091593 msecs"
(defn commarize4
[num-str]
;;reverse the string since the first comma starts from the right
;;side
(let [buckets (range (dec (count num-str)) -1 -1)]
(->> buckets
(into '() (comp (partition-all 3)
(map (fn [idxs]
(mapv #(.charAt ^String num-str %) idxs)))
(interpose ",")
cat))
clojure.string/join)))
(defn decimal [num-str]
(re-find #"\.[0-9]+" num-str))
;;handles decimals...
(defn commarize5
[num-str]
(if-let [d (decimal num-str)]
(str (commarize4 (subs num-str 0 (- (count num-str) (count d))))
d)
(commarize4 num-str)))
;;numberdemo> (time (dotimes [i 1000000] (commarize5 "1000000")))
;;"Elapsed time: 2314.282565 msecs"
;;using java's built-in format:
;;doesn't work with decimals,
;;way faster
(defn commarize6 [s]
(format "%,d" (numericize s)))
;;numberdemo> (time (dotimes [i 1000000] (commarize3f "1000000")))
;;"Elapsed time: 1189.800786 msecs"
(defn commarize7
[num-str]
(if-let [d (decimal num-str)]
(str (commarize6 (subs num-str 0 (- (count num-str) (count d))))
d)
(commarize6 num-str)))
;;numberdemo> (time (dotimes [i 1000000] (commarize7 "1000000")))
;;"Elapsed time: 1245.402081 msecs"
(defn commarize-by
[cf decf num-str]
(if-let [d (decf num-str)]
(str (cf (subs num-str 0 (- (count num-str) (count d))))
d)
(cf num-str)))
;;a little function call overhead.
;;numberdemo> (time (dotimes [i 1000000] (commarize-by commarize6 decimal "1000000")))
;;"Elapsed time: 1342.958075 msecs"
(defn as-number [n]
(cond (string? n) (let [^String x n]
(try (Integer/parseInt x)
(catch NumberFormatException _
(Double/parseDouble x))))
(number? n) n
:else (throw (ex-info "can't number it!" {:input n}))))
;; numberdemo> (time (dotimes [i 1000000] (as-number "1000000")))
;; "Elapsed time: 27.53892 msecs"
;; numberdemo> (time (dotimes [i 1000000] (numericize "1000000")))
;; "Elapsed time: 517.713749 msecs"
(defn commarize8 [s]
(format "%,d" (as-number s)))
(defn commarize9
[num-str]
(if-let [d (decimal num-str)]
(str (commarize8 (subs num-str 0 (- (count num-str) (count d))))
d)
(commarize8 num-str)))
;;numberdemo> (time (dotimes [i 1000000] (commarize8 "1000000")))
;;"Elapsed time: 686.508348 msecs"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment