A Decimal to Roman Numeral converter in Clojure

I stumbled upon the blog entry A Decimal to Roman Numeral converter in just a few lines where a Clojure function with loop/recur was presented. The solution was very concise yet it used loop/recur which I decided to replace with the functional trio – map, reduce and filter.

I knew I could make it, but after a nothing-but-scratching-my-head hour or two I began googling for a hint. After a sec Google pointed me to using reduce instead of loop recur and life turned bright. Or so thought I.

The first version was as follows. Lots of parentheses, two first‘s at the beginning and although I made out well – no loop/recur combo, I was far from being proud of my achievement. I called it a partial success, but learnt a lot – drop-while and iterate were a bonus.

(def mapping (sorted-map-by > 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"))

(defn arabic->roman [n]
  (first
    (first
      (drop-while (comp (complement nil?) second)
                  (iterate
                    (fn [[reminder roman]]
                      (if (zero? reminder)
                        [roman nil]
                        (let [curr-num (first (filter (partial >= reminder) (keys mapping)))]
                          [(- reminder curr-num) (str roman (get mapping curr-num))])))
                    [n ""])))))

And the other three days passed as I was tinkering the solution above to find a better version with map/reduce/filter trio. I believe that to truly appreciate expressiveness of functional programming one has to solve problems with it. Here’s the other version with reduce and filter. Not bad, isn’t it?

(defn arabic->roman [n]
  (:roman
    (reduce
      (fn [state rv]
        (let [{:keys [roman number]} state
              [arabic-num roman-number] rv
              quotient (quot number arabic-num)]
          (if (>= quotient 0)
            {:roman  (str roman (apply str (repeat quotient roman-number)))
             :number (- number (* quotient arabic-num))}
            state)))
      {:roman "" :number n}
      (-> #(>= n (first %))
        (filter mapping)))))

But it’s still ugly, too verbose, reads badly and so does it look. I tried to spice it up a bit with destructuring to make the code a little smaller, but still it’s too cumbersome. I wish I had come up with a better version, but it will take time for sure. Hopefully, someone will share a better version of the problem with the functional trio – map, reduce and filter that will look better, slimmer and effective. If it could attract a Clojure novice’s eye, it’d be so much better. Anyone? Thanks!

p.s. Have a read of the comments in the blog entry A Decimal to Roman Numeral converter in just a few lines where you can find a solution with clojure.pprint/cl-format. What’s a one-liner! The function is a part of the Clojure library and it’s obvious now (after I’ve seen it) that the conversion should really be part of the clojure.pprint.

Be Sociable, Share!
This entry was posted in Languages.

4 Responses to A Decimal to Roman Numeral converter in Clojure

  1. Lukasz Kuczera says:

    I’d love to see syntax coloring here :E

  2. Bob Shock says:

    This is one of the problems on 4clojure. Here is my solution, for the sake of comparison:

    (fn [roman]
    (apply str
      (let [roman (str roman)
            i "IXCM"
            v "VLD"
            x "XCM"
            ms  {\1 [i] \2 [i i] \3 [i i i]
                 \4 [i v] \5 [v] \6 [v i]
                 \7 [v i i] \8 [v i i i] \9[i x]}
            powers (reverse (range (count roman)))]
            (mapcat
              (fn [power digit]
                 (map
                   #((vec %) power)
                   (ms digit)))
                powers
                roman))))
    • Thanks for the solution. A very bright one and I could learn a lot! It once again proves that without algorithmic background every problem can become way too hard to solve in any language (which is just another way to write (executable) sentences). I believe, thought, that with more and more such small(ish) functional applications I’ll spot patterns and apply right solutions more quicker. Thanks!

      One point: I’d change

      (apply str ...)

      to

      (reduce str ...)

      around

      mapcat

      .

Leave a Reply

%d bloggers like this: