Skip to content

Instantly share code, notes, and snippets.

@miner
Created August 6, 2012 14:16
Show Gist options
  • Select an option

  • Save miner/3274712 to your computer and use it in GitHub Desktop.

Select an option

Save miner/3274712 to your computer and use it in GitHub Desktop.
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
Copy link
Author

miner commented May 23, 2014

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment