- Grow an algorithm bit by bit
- Delay treating exceptions (in this case, because they are more complex)
- Intentionally cause duplication
- Focus on simple structures first
I think the reason is that the first time I started refactoring too soon and that hid the duplication pattern that would have lead me to this simpler solution.
As in the previous version, I extended the kata to also convert decimal numbers over 3999.
This is the resulting code:
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 roman-numerals-converter.core) | |
(declare | |
to-roman-from-up-to-3999 | |
to-roman-from-over-3999) | |
(defn to-roman [decimal] | |
(if (<= decimal 3999) | |
(to-roman-from-up-to-3999 decimal) | |
(to-roman-from-over-3999 decimal))) | |
;; | |
;; Decimal numbers up to 3999 | |
;; | |
(def ^:private | |
decs-to-roms | |
[{:dec 1000 :rom "M"} | |
{:dec 900 :rom "CM"} | |
{:dec 500 :rom "D"} | |
{:dec 400 :rom "CD"} | |
{:dec 100 :rom "C"} | |
{:dec 90 :rom "XC"} | |
{:dec 50 :rom "L"} | |
{:dec 40 :rom "XL"} | |
{:dec 10 :rom "X"} | |
{:dec 9 :rom "IX"} | |
{:dec 5 :rom "V"} | |
{:dec 4 :rom "IV"} | |
{:dec 1 :rom "I"}]) | |
(defn- to-roman-from-up-to-3999 [decimal] | |
(loop [acc "" | |
decimal decimal | |
decs-to-roms decs-to-roms] | |
(if (zero? decimal) | |
acc | |
(let [dec-to-rom (first decs-to-roms)] | |
(if (>= decimal (:dec dec-to-rom)) | |
(recur (str acc (:rom dec-to-rom)) | |
(- decimal (:dec dec-to-rom)) | |
decs-to-roms) | |
(recur acc | |
decimal | |
(rest decs-to-roms))))))) | |
;; | |
;; Decimal numbers over 3999 | |
;; | |
(def ^:private join-str | |
(partial apply str)) | |
(defn- times-thousand-bar [multiple] | |
(concat (repeat (count multiple) "-") "\n")) | |
(defn- times-thousand-bars [multiple times] | |
(join-str | |
(flatten | |
(repeat times | |
(times-thousand-bar multiple))))) | |
(defn- multiple [decimal] | |
(loop [times 0 num decimal] | |
(if (<= num 3999) | |
[(to-roman-from-up-to-3999 num) times] | |
(recur (inc times) (quot num 1000))))) | |
(defn- to-roman-from-over-3999 [decimal] | |
(let [[multiple times] (multiple decimal)] | |
(str (times-thousand-bars multiple times) | |
multiple | |
(to-roman-from-up-to-3999 | |
(rem decimal (* times 1000)))))) |
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 roman-numerals-converter.core-test | |
(:use midje.sweet) | |
(:use [roman-numerals-converter.core])) | |
(facts | |
"about Roman numerals converter" | |
(fact | |
"converts decimal numbers up to 3999 into roman numbers" | |
(to-roman 1) => "I" | |
(to-roman 2) => "II" | |
(to-roman 3) => "III" | |
(to-roman 4) => "IV" | |
(to-roman 5) => "V" | |
(to-roman 6) => "VI" | |
(to-roman 8) => "VIII" | |
(to-roman 9) => "IX" | |
(to-roman 10) => "X" | |
(to-roman 11) => "XI" | |
(to-roman 18) => "XVIII" | |
(to-roman 25) => "XXV" | |
(to-roman 33) => "XXXIII" | |
(to-roman 34) => "XXXIV" | |
(to-roman 39) => "XXXIX" | |
(to-roman 40) => "XL" | |
(to-roman 50) => "L" | |
(to-roman 90) => "XC" | |
(to-roman 100) => "C" | |
(to-roman 400) => "CD" | |
(to-roman 500) => "D" | |
(to-roman 900) => "CM" | |
(to-roman 1000) => "M" | |
(to-roman 2499) => "MMCDXCIX" | |
(to-roman 3949) => "MMMCMXLIX" | |
(to-roman 3999) => "MMMCMXCIX") | |
(fact | |
"converts decimal numbers over 3999 into roman numbers" | |
(to-roman 4000) => "--\nIV" | |
(to-roman 4001) => "--\nIVI" | |
(to-roman 30000) => "---\nXXX" | |
(to-roman 3999005) => "---------\nMMMCMXCIXV" | |
(to-roman 4000000) => "--\n--\nIV" | |
(to-roman 4000025) => "--\n--\nIVXXV")) |
This time it was a bit easier to derive the algorithm only using strict TDD.
You can find all the code in this repository in GitHub.
No comments:
Post a Comment