In my waiting-for-people-to-leave-so-I-can-get-into-classrooms time at work I wrote a roman numeral translator in Factor. It’s a bit different from your normal implementation as Factor’s parser actually does almost all the work:
USING: strings parser kernel words sequences math ;
: NUMERAL: CREATE dup reset-generic dup t "parsing" set-word-prop parse-definition parsed add define-compound ; parsing
NUMERAL: I 1 ;
NUMERAL: IV 4 ;
NUMERAL: V 5 ;
NUMERAL: IX 9 ;
NUMERAL: X 10 ;
NUMERAL: XL 40 ;
NUMERAL: L 50 ;
NUMERAL: XC 90 ;
NUMERAL: C 100 ;
NUMERAL: CD 400 ;
NUMERAL: D 500 ;
NUMERAL: CM 900 ;
NUMERAL: M 1000 ;
: separate ( str -- str )
"" swap [ " " append ] [ add ] interleave ;
: join-special ( str str -- str )
dup >r split1 [ 1 r> remove-nth swap 3append ] [ r> drop ] if* ;
: merge-specials ( str -- str )
[ "I V" "I X" "X L" "X C" "C D" "C M" ] [ join-special ] each ;
: convert-numerals ( string -- arr )
separate merge-specials parse ;
: all-numerals? ( str -- ? )
[ "IVXLCDM" member? ] all? ;
: roman>number ( roman -- number )
>upper dup all-numerals? [ convert-numerals sum ] [ drop "Not a roman numeral" ] if ;
Instead of grabbing characters and keeping a running tally, I defined a bunch of parsing words using NUMERAL:
to hold the values. I then took the string and split it into individual characters (“XIV” becomes “X I V”). The 4’s and 9’s are then rejoined (so we get “X IV”). I then simply parse the string, which gives a list of numbers and sum that up. It’s not perfect as it allows any pattern of numerals (“IVIVIVIV” parsed to 22), but good enough.