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

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

romannum.gno

2.20 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/nym-moultest123/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}