Skip to content

Instantly share code, notes, and snippets.

@miner miner/roman.clj

Created Aug 6, 2012
Embed
What would you like to do?
Roman Numerals
(ns miner.roman
(:require [clojure.test :refer :all]))
;; inspired by
;; http://www.jayway.com/2012/08/04/a-decimal-to-roman-numeral-converter-in-just-a-few-lines/
(def roman-map {1000 "M" 900 "CM" 500 "D" 400 "CD"
100 "C" 90 "XC" 50 "L" 40 "XL"
10 "X" 9 "IX" 5 "V" 4 "IV" 1 "I"})
(def roman-bases (sort > (keys roman-map)))
(defn roman-addends [n]
{:pre [(< 0 n 4000)]}
(loop [n n bases roman-bases addends []]
(if (zero? n)
addends
(let [base (first bases)]
(if (>= n base)
(recur (- n base) bases (conj addends base))
(recur n (rest bases) addends))))))
(defn roman [n]
(apply str (map roman-map (roman-addends n))))
;; clojure.pprint/cl-format works but is slow
(defn roman-cl [n]
(clojure.pprint/cl-format nil "~@R" n))
(deftest roman-4k
(doseq [n (range 1 4000)]
(is (= (roman-cl n) (roman n)))))
(def inv-roman-char-map
{\M 1000 \D 500 \C 100 \L 50 \X 10 \V 5 \I 1})
(defn roman-values-seq [rn]
(let [vs (rseq (conj (mapv inv-roman-char-map rn) 0))]
;; going in reverse order is easier,
;; padded zero to simplify prev
(map (fn [v prev] (if (>= v prev) v (- v))) (rest vs) vs)))
(defn parse-roman [rn]
(reduce + (roman-values-seq rn)))
(deftest parse-roman-test
(doseq [n (range 1 4000)]
(is (= n (parse-roman (roman n))))))
@miner

This comment has been minimized.

Copy link
Owner Author

miner commented May 23, 2014

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.