Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

romannum.gno

2.19 Kb · 96 lines
 1// Package romannum is a pure port of the classic Roman-numeral converter kata:
 2// ToRoman / FromRoman, valid for 1..3999. Deterministic — no time, randomness
 3// or I/O — and free of any realm coupling, so it is reusable as a library.
 4//
 5// A live demo of this package (an interactive integer↔Roman converter) is at
 6// [r/moul/x/daily/romannumdemo](/r/moul/x/daily/romannumdemo/v0).
 7package romannum
 8
 9import (
10	"strconv"
11	"strings"
12)
13
14// romanUnits is the greedy subtractive-notation table, largest value first.
15var romanUnits = []struct {
16	val int
17	sym string
18}{
19	{1000, "M"}, {900, "CM"}, {500, "D"}, {400, "CD"},
20	{100, "C"}, {90, "XC"}, {50, "L"}, {40, "XL"},
21	{10, "X"}, {9, "IX"}, {5, "V"}, {4, "IV"}, {1, "I"},
22}
23
24// ToRoman renders an integer in 1..3999 as a Roman numeral.
25// It panics if n is out of range.
26func ToRoman(n int) string {
27	if n < 1 || n > 3999 {
28		panic("romannum: out of range (want 1..3999): " + strconv.Itoa(n))
29	}
30	var b strings.Builder
31	for _, u := range romanUnits {
32		for n >= u.val {
33			b.WriteString(u.sym)
34			n -= u.val
35		}
36	}
37	return b.String()
38}
39
40// FromRoman parses a Roman numeral back to an integer.
41// It panics on any malformed input (e.g. "IIII", "IC", "VV").
42func FromRoman(s string) int {
43	n, ok := parseRoman(s)
44	if !ok {
45		panic("romannum: invalid roman numeral: " + s)
46	}
47	return n
48}
49
50// charVal maps a single Roman digit to its value, or 0 if unknown.
51func charVal(c byte) int {
52	switch c {
53	case 'I':
54		return 1
55	case 'V':
56		return 5
57	case 'X':
58		return 10
59	case 'L':
60		return 50
61	case 'C':
62		return 100
63	case 'D':
64		return 500
65	case 'M':
66		return 1000
67	}
68	return 0
69}
70
71// parseRoman is the pure, panic-free core used by FromRoman.
72// It returns (value, true) only for a canonical numeral: it accepts input
73// iff ToRoman(value) reproduces it exactly, which rejects malformed forms.
74func parseRoman(s string) (int, bool) {
75	s = strings.ToUpper(strings.TrimSpace(s))
76	if s == "" {
77		return 0, false
78	}
79	total, prev := 0, 0
80	for i := len(s) - 1; i >= 0; i-- {
81		v := charVal(s[i])
82		if v == 0 {
83			return 0, false
84		}
85		if v < prev {
86			total -= v
87		} else {
88			total += v
89			prev = v
90		}
91	}
92	if total < 1 || total > 3999 || ToRoman(total) != s {
93		return 0, false
94	}
95	return total, true
96}