Bad luck and mathematica
Weekly challenge 227 — 24 July 2023
Week 227: 24 Jul 2023
Write a script to handle a 2-term arithmetic operation expressed in Roman numerals.
IV + V => IX M - I => CMXCIX X / II => V XI * VI => LXVI VII ** III => CCCXLIII V - V => nulla (they knew about zero but didn't have a symbol) V / II => non potest (they didn't do fractions) MMM + M => non potest (they only went up to 3999) V - X => non potest (they didn't do negative numbers)
The Perly way to do this seems like having two functions, decode_roman()
and
encode_roman()
. We can then parse the input into $first, $op, $second
and the answer will be
encode_roman(
.
eval(decode_roman($first) . $op . decode_roman($second)))
And with a few tweaks to handle the special cases, that's what I did.
In both decode and encode the principle is to decompose the input into its roman constituents - ie 1000 (M), 900 (CM), 500 (D) ... and so on, steadily building up the result.
For decode, we need to start with the 2-character roman components - IV, IX and so on - and then the single character ones. For encode we can simply go greatest to least - 1000, 900, 500 ...
(Disclosure: I submitted this challenge - before I tried solving it).
#!/usr/bin/perl use v5.16; # The Weekly Challenge - 2023-07-24 use utf8; # Week 227 task 2 - Roman maths use strict; # Peter Campbell Smith use warnings; # Blog: http://ccgi.campbellsmiths.force9.co.uk/challenge roman_maths('IV + V'); roman_maths('M - I'); roman_maths('X / II'); roman_maths('XI * VI'); roman_maths('VII ** III'); roman_maths('V - V'); roman_maths('V / II'); roman_maths('V - X'); roman_maths('LXXX * L'); roman_maths('XLIII * XCIII'); sub roman_maths { my ($first, $op, $second, $result); say qq[\nInput: $_[0]]; # split input string into operands and operator if ($_[0] =~ m|^\s*([IVXLCDM]+)\s*(\S{1,2})\s*([IVXLCDM]+)\s*$|) { ($first, $op, $second) = ($1, $2, $3); # if the operator is valid, evaluate the operation if ($op =~ m!^-|\+|\*|/|\*\*$!) { $result = encode_roman(eval(decode_roman($first) . $op . decode_roman($second))); say qq[Output: $result]; } } say qq[invalid input] unless defined $result; } sub decode_roman { # convert roman to arabic my (@bits, $roman, $arabic, $n); # roman fragments and equivalents @bits = ('IV', 4, 'IX', 9, 'XL', 40, 'XC', 90, 'CD', 400, 'CM', 900, 'I', 1, 'V', 5, 'X', 10, 'L', 50, 'C', 100, 'D', 500, 'M', 1000); # successively delete bits from $roman and add them to $arabic $roman = $_[0]; $arabic = 0; for ($n = 0; $n < @bits; $n += 2) { $arabic += $bits[$n + 1] * $roman =~ s|$bits[$n]||g; } return $arabic; } sub encode_roman { # convert arabic to roman my (@bits, $n, $arabic, $roman); # roman fragments and equivalents @bits = ('M', 1000, 'CM', 900, 'D', 500, 'CD', 400, 'C', 100, 'XC', 90, 'L', 50, 'XL', 40, 'X', 10, 'IX', 9, 'V', 5, 'IV', 4, 'I', 1); # special cases $arabic = $_[0]; return 'nulla' if $arabic == 0; return 'non potest' if ($arabic < 0 or $arabic > 3999 or $arabic != int($arabic)); # successively subtract bits from $arabic and add them to $roman $roman = ''; for ($n = 0; $n < @bits; $n += 2) { if ($arabic >= $bits[$n + 1]) { # may have to repeat some roman symbols - eg XXX while ($arabic >= $bits[$n + 1]) { $roman .= $bits[$n]; $arabic -= $bits[$n + 1]; } } } return $roman; }
Input: IV + V Output: IX Input: M - I Output: CMXCIX Input: X / II Output: V Input: XI * VI Output: LXVI Input: VII ** III Output: CCCXLIII Input: V - V Output: nulla Input: V / II Output: non potest Input: V - X Output: non potest Input: LXXX * L Output: non potest Input: XLIII * XCIII Output: MMMCMXCIX
Any content of this website which has been created by Peter Campbell Smith is in the public domain