Created
June 26, 2019 20:41
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(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