Skip to content

Instantly share code, notes, and snippets.

@Crowbrammer
Last active April 25, 2022 20:02
Show Gist options
  • Save Crowbrammer/dcc7b94dcc32aa9a2212cd907ec02990 to your computer and use it in GitHub Desktop.
Save Crowbrammer/dcc7b94dcc32aa9a2212cd907ec02990 to your computer and use it in GitHub Desktop.
Roman Numerals Decoder
(ns translate-roman-numerals.test
(require [clojure.test :refer :all]
[translate-roman-numerals.core :refer :all]))
(deftest give-values-test
(are [given-value calculated-value] (= given-value calculated-value)
2 (letters-value \I 2)
2 (letters-value \I 4)
10 (letters-value \V 2)
10 (letters-value \V 2)
20 (letters-value \X 2)
20 (letters-value \X 2)
0 (letters-value \X 0)
50 (letters-value \L 1)
300 (letters-value \C 3)
200 (letters-value \C 2)
500 (letters-value \D 1)
3000 (letters-value \M 3)))
(deftest sample-roman-numerals
(are [given-value calculated-value] (= given-value calculated-value)
1 (translate-roman-numerals "I")
4 (translate-roman-numerals "IV")
2008 (translate-roman-numerals "MMVIII")
1666 (translate-roman-numerals "MDCLXVI")))
(ns translate-roman-numerals.core
"Turn Roman numerals into Arabic numerals"
(:require [clojure.string :as str]))
(defn letters-value
"Given a Roman character and its number of occurences
(perhaps from `frequencies`), give its total value. Once
it hits 4, the subtraction principle kicks in, and it's
effectively equal two letter occurences. Assumes correct
Roman numeral notation.
This fails with IV, XL, XC, CD, and CM, so a correction must
be applied outside the function for these results."
[ch freq]
(let [char-values {\I 1
\V 5
\X 10
;; L should prolly not be used more than once due to C
\L 50
\C 100
;; Same with D
\D 500
;; Interestingly, there's nothing after M.
;; You use bars https://qr.ae/pv24MV
\M 1000}
;; There can never be more than four numerals.
;; At four, you subtract the last one, which means
;; +3 -1 for 2. At four letters, you effectively have
;; 2. Make that the freq.
;; The only exception is the I's can have one :( )
actualized-freq (case freq
0 0
1 1
2 2
3 3
4 2)]
(* (get char-values ch 0) actualized-freq)))
(defn translate-roman-numerals
[roman]
;; You can handle the subtraction principle in two ways: order and
;; frequency. Order can be used to determine all necessary subtraction,
;; and order must be considered for "IV", "XL","XC", "CD", and "CM". There
;; can never be more than three identical letters without the subtraction
;; principle, so the fourth subtracts, thus `letters-value`. When using
;; the frequency approach, we must conditionally handle these four cases.
;; For a ridiculous point because performance isn't needed, I suspect this
;; is faster and simpler than the order-only approach.
(let [fqs (frequencies roman)
Arabic-vals (map #(apply letters-value %) fqs)
prelim-result (apply + Arabic-vals)]
;; 1) Undoing the "mistake" of adding when subtracting, and then
;; 2) Subtracting from the original number makes two a theme.
(cond-> prelim-result
(str/includes? roman "IV") (- 2) ;; Should be four. Will be six.
(str/includes? roman "XL") (- 20) ;; Should be forty. Will be sixty.
(str/includes? roman "XC") (- 20) ;; Should be ninety. Will be 110.
(str/includes? roman "CD") (- 200) ;; Should be 400. Will be 600.
(str/includes? roman "CM") (- 200) ;; Should be 900. Will be 1100.
)
))
;; Random question: This site, http://www.romannumerals.co/learn/subtractive-principle/,
;; says "The subtractive principle, applies to the numbers 4, 9, 40, 90, 400 and 900 only.".
;; Is this true?
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment