Skip to content

Commit 68c9fdd

Browse files
committed
feat: add Roman numeral conversion functions
- IntToRoman(n int) string - converts integers 1-3999 to Roman numerals - RomanToInt(s string) (int, error) - parses Roman numerals with validation - Engine methods for both functions Validation rules: - Only valid characters (I, V, X, L, C, D, M) - Max 3 consecutive identical numerals (I, X, C, M) - V, L, D cannot repeat - Valid subtractive combinations only (IV, IX, XL, XC, CD, CM) Closes: go-inflect-q2z
1 parent 3dc1184 commit 68c9fdd

5 files changed

Lines changed: 676 additions & 0 deletions

File tree

inflect_gen.go

Lines changed: 40 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/inflect/example_test.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1318,3 +1318,49 @@ func ExampleEngine_FuncMap() {
13181318
// Output:
13191319
// The plural of gremlin is gremloz
13201320
}
1321+
1322+
// --- Roman numeral examples ---
1323+
1324+
func ExampleIntToRoman() {
1325+
fmt.Println(inflect.IntToRoman(4))
1326+
fmt.Println(inflect.IntToRoman(9))
1327+
fmt.Println(inflect.IntToRoman(42))
1328+
fmt.Println(inflect.IntToRoman(1984))
1329+
fmt.Println(inflect.IntToRoman(2025))
1330+
// Output:
1331+
// IV
1332+
// IX
1333+
// XLII
1334+
// MCMLXXXIV
1335+
// MMXXV
1336+
}
1337+
1338+
func ExampleRomanToInt() {
1339+
n1, _ := inflect.RomanToInt("IV")
1340+
n2, _ := inflect.RomanToInt("XIV")
1341+
n3, _ := inflect.RomanToInt("MCMLXXXIV")
1342+
n4, _ := inflect.RomanToInt("MMXXV")
1343+
fmt.Println(n1)
1344+
fmt.Println(n2)
1345+
fmt.Println(n3)
1346+
fmt.Println(n4)
1347+
// Output:
1348+
// 4
1349+
// 14
1350+
// 1984
1351+
// 2025
1352+
}
1353+
1354+
func ExampleRomanToInt_lowercase() {
1355+
n, _ := inflect.RomanToInt("xiv")
1356+
fmt.Println(n)
1357+
// Output:
1358+
// 14
1359+
}
1360+
1361+
func ExampleRomanToInt_error() {
1362+
_, err := inflect.RomanToInt("IIII")
1363+
fmt.Println(err)
1364+
// Output:
1365+
// invalid Roman numeral
1366+
}

internal/inflect/fuzz_test.go

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1307,3 +1307,95 @@ func FuzzCamelizeVariants(f *testing.F) {
13071307
_ = CamelizeDownFirst(input)
13081308
})
13091309
}
1310+
1311+
// Covers: IntToRoman.
1312+
func FuzzIntToRoman(f *testing.F) {
1313+
seeds := []int{
1314+
// Edge cases
1315+
0, 1, -1,
1316+
// Basic numerals
1317+
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
1318+
// Subtractive combinations
1319+
4, 9, 40, 90, 400, 900,
1320+
// Key boundaries
1321+
50, 100, 500, 1000,
1322+
// Complex numbers
1323+
42, 1984, 2024, 2025, 3999,
1324+
// Out of range
1325+
4000, 5000, -100,
1326+
// Large values
1327+
math.MaxInt32, math.MinInt32,
1328+
}
1329+
for _, n := range seeds {
1330+
f.Add(n)
1331+
}
1332+
1333+
f.Fuzz(func(t *testing.T, input int) {
1334+
result := IntToRoman(input)
1335+
if input < 1 || input > 3999 {
1336+
// Out of range should return empty
1337+
if result != "" {
1338+
t.Errorf("IntToRoman(%d) should return empty, got %q", input, result)
1339+
}
1340+
return
1341+
}
1342+
// Valid range should produce non-empty result
1343+
if result == "" {
1344+
t.Errorf("IntToRoman(%d) returned empty string", input)
1345+
}
1346+
// Round-trip should work
1347+
parsed, err := RomanToInt(result)
1348+
if err != nil {
1349+
t.Errorf("RomanToInt(%q) failed: %v", result, err)
1350+
}
1351+
if parsed != input {
1352+
t.Errorf("Round-trip failed: %d -> %q -> %d", input, result, parsed)
1353+
}
1354+
})
1355+
}
1356+
1357+
// Covers: RomanToInt.
1358+
func FuzzRomanToInt(f *testing.F) {
1359+
seeds := []string{
1360+
// Valid numerals
1361+
"I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", "X",
1362+
"XI", "XIV", "XIX", "XX", "XL", "L", "XC", "C",
1363+
"CD", "D", "CM", "M", "MM", "MMM",
1364+
"MCMLXXXIV", "MMXXIV", "MMXXV", "MMMCMXCIX",
1365+
// Lowercase
1366+
"i", "iv", "xiv", "mcmlxxxiv",
1367+
// Invalid
1368+
"", " ", "IIII", "VV", "LL", "DD", "MMMM",
1369+
"IL", "IC", "ID", "IM", "VX", "XD", "XM",
1370+
"ABC", "123", "XIV2",
1371+
// Edge cases
1372+
"iIiI", "IVIV", "IXI",
1373+
}
1374+
for _, s := range seeds {
1375+
f.Add(s)
1376+
}
1377+
1378+
f.Fuzz(func(t *testing.T, input string) {
1379+
if !utf8.ValidString(input) {
1380+
return
1381+
}
1382+
result, err := RomanToInt(input)
1383+
if err != nil {
1384+
// Errors are expected for invalid input - no assertion needed
1385+
return
1386+
}
1387+
// Valid parse - verify round-trip
1388+
if result < 1 || result > 3999 {
1389+
t.Errorf("RomanToInt(%q) returned out-of-range value: %d", input, result)
1390+
}
1391+
// Convert back and re-parse
1392+
roman := IntToRoman(result)
1393+
reparsed, err := RomanToInt(roman)
1394+
if err != nil {
1395+
t.Errorf("Re-parse of %q failed: %v", roman, err)
1396+
}
1397+
if reparsed != result {
1398+
t.Errorf("Re-parse mismatch: %q -> %d -> %q -> %d", input, result, roman, reparsed)
1399+
}
1400+
})
1401+
}

0 commit comments

Comments
 (0)