diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml deleted file mode 100644 index 83c49e9..0000000 --- a/.github/workflows/go.yml +++ /dev/null @@ -1,36 +0,0 @@ -name: Test Coverage - -on: - push: - branches: [ master ] - pull_request: - branches: [ master ] - -jobs: - - build: - name: Build - runs-on: ubuntu-latest - steps: - - - name: Set up Go 1.x - uses: actions/setup-go@v2 - with: - go-version: ^1.13 - - - name: Check out code into the Go module directory - uses: actions/checkout@v2 - - - name: Build - run: go build -v . - - - name: Test - run: go test -race -covermode=atomic -coverprofile ./cp.out - - - name: Codecov - # You may pin to the exact commit or the version. - # uses: codecov/codecov-action@7d5dfa54903bd909319c580a00535b483d1efcf3 - uses: codecov/codecov-action@v1.0.14 - with: - token: ${{ secrets.CODECOV_TOKEN }} - file: ./cp.out diff --git a/README.md b/README.md index 62ec6dd..2ce280b 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,21 @@ # Go Roman Numerals -[![Go Reference](https://pkg.go.dev/badge/github.com/brandenc40/romannumeral.svg)](https://pkg.go.dev/github.com/brandenc40/romannumeral) -[![codecov](https://codecov.io/gh/brandenc40/romannumeral/branch/master/graph/badge.svg?token=AS7IBSTE36)](https://codecov.io/gh/brandenc40/romannumeral) - -## Quickly and efficiently convert to and from roman numerals in Go. +This package was adapted from [romannumeral](https://github.com/brandenc40/romannumeral) to fit my specific minimal use-case. -A reliable module using the most efficient methods possible for converting between -roman numerals and integers in Go. Algorithms adopted from [here](https://rosettacode.org/wiki/Roman_numerals). +All functionality except for the ability to convert integers to roman numerals has been removed. + +If you need any additional functionality or further documentation, please see the original package. ### Benchmark Results ```sh -goos: darwin -goarch: arm64 -pkg: github.com/brandenc40/romannumeral -BenchmarkIntToString-8 56474846 20.84 ns/op 0 B/op 0 allocs/op -BenchmarkIntToBytes-8 48157634 24.36 ns/op 0 B/op 0 allocs/op -BenchmarkStringToInt-8 17584252 67.28 ns/op 0 B/op 0 allocs/op -BenchmarkBytesToInt-8 18343551 64.77 ns/op 0 B/op 0 allocs/op +goos: linux +goarch: amd64 +pkg: github.com/rwinkhart/convertroman +cpu: AMD Ryzen 9 3900X 12-Core Processor +BenchmarkFromInt-24 62046414 21.08 ns/op 0 B/op 0 allocs/op PASS -ok github.com/brandenc40/romannumeral 6.111s +ok github.com/rwinkhart/convertroman 1.332s ``` ### Example @@ -29,38 +25,14 @@ package main import ( "fmt" - rom "github.com/brandenc40/romannumeral" + rom "github.com/rwinkhart/convertroman" ) -func ExampleStringToInt() { - integer, err := rom.StringToInt("IV") - if err != nil { - panic(err) - } - fmt.Println(integer == 4) // True -} - -func ExampleBytesToInt() { - integer, err := rom.BytesToInt([]byte("IV")) - if err != nil { - panic(err) - } - fmt.Println(integer == 4) // True -} - -func ExampleIntToString() { - roman, err := rom.IntToString(4) +func ExampleFromInt() { + roman, err := rom.FromInt(4) if err != nil { panic(err) } fmt.Println(roman == "IV") // True } - -func ExampleIntToBytes() { - roman, err := rom.IntToBytes(4) - if err != nil { - panic(err) - } - fmt.Println(string(roman) == "IV") // True -} ``` diff --git a/go.mod b/go.mod index 78a7687..3d6f228 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ -module github.com/brandenc40/romannumeral +module github.com/rwinkhart/convertroman go 1.15 diff --git a/go.sum b/go.sum deleted file mode 100644 index e69de29..0000000 diff --git a/romannumeral.go b/romannumeral.go index ac29dec..871e2ad 100644 --- a/romannumeral.go +++ b/romannumeral.go @@ -1,109 +1,33 @@ -// Converts between integers and Roman Numeral strings. -// +package convertroman + // Currently only supports Roman Numerals without viniculum (1-3999) and will throw an error for // numbers outside of that range. See here for details on viniculum: // https://en.wikipedia.org/wiki/Roman_numerals#Large_numbers -package romannumeral import ( - "bytes" "errors" ) -// numeral describes the value and symbol of a single roman numeral -type numeral struct { - val int - sym []byte -} - var ( - // InvalidRomanNumeral - error for when a roman numeral string provided is not a valid roman numeral - InvalidRomanNumeral = errors.New("invalid roman numeral") // IntegerOutOfBounds - error for when the integer provided is invalid and unable to be converted to a roman numeral IntegerOutOfBounds = errors.New("integer must be between 1 and 3999") - // all unique numerals ordered from largest to smallest - nums = []numeral{ - {1000, []byte("M")}, - {900, []byte("CM")}, - {500, []byte("D")}, - {400, []byte("CD")}, - {100, []byte("C")}, - {90, []byte("XC")}, - {50, []byte("L")}, - {40, []byte("XL")}, - {10, []byte("X")}, - {9, []byte("IX")}, - {5, []byte("V")}, - {4, []byte("IV")}, - {1, []byte("I")}, - } - // lookup arrays used for converting from an int to a roman numeral extremely quickly. // method from here: https://rosettacode.org/wiki/Roman_numerals/Encode#Go - r0 = []string{"", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX"} - r1 = []string{"", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC"} - r2 = []string{"", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM"} - r3 = []string{"", "M", "MM", "MMM"} + r0 = [10]string{"", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX"} + r1 = [10]string{"", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC"} + r2 = [10]string{"", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM"} + r3 = [4]string{"", "M", "MM", "MMM"} ) -// IntToString converts an integer value to a roman numeral string. An error is -// returned if the integer is not between 1 and 3999. -func IntToString(input int) (string, error) { - if outOfBounds(input) { - return "", IntegerOutOfBounds +// FromInt converts an integer value to a roman numeral string. +// An error is returned if the integer is not between 1 and 3999. +func FromInt(input int) (string, error) { + // ensure provided integer is within the valid range + if input < 1 || input > 3999 { + return "OOB", IntegerOutOfBounds } - return intToRoman(input), nil -} -// IntToBytes converts an integer value to a roman numeral byte array. An error is -// returned if the integer is not between 1 and 3999. -func IntToBytes(input int) ([]byte, error) { - str, err := IntToString(input) - return []byte(str), err -} - -// outOfBounds checks to ensure an input value is valid for roman numerals without the need of -// vinculum (used for values of 4,000 and greater) -func outOfBounds(input int) bool { - return input < 1 || input > 3999 -} - -func intToRoman(n int) string { - // This is efficient in Go. The 4 operands are evaluated, - // then a single allocation is made of the exact size needed for the result. - return r3[n%1e4/1e3] + r2[n%1e3/1e2] + r1[n%100/10] + r0[n%10] -} - -// StringToInt converts a roman numeral string to an integer. Roman numerals for numbers -// outside of the range 1 to 3,999 will return an error. Empty strings will return 0 -// with no error thrown. -func StringToInt(input string) (int, error) { - return BytesToInt([]byte(input)) -} - -// BytesToInt converts a roman numeral byte array to an integer. Roman numerals for numbers -// outside of the range 1 to 3,999 will return an error. Nil or empty []byte will return 0 -// with no error thrown. -func BytesToInt(input []byte) (int, error) { - if input == nil || len(input) == 0 { - return 0, nil - } - if output, ok := romanToInt(input); ok { - return output, nil - } - return 0, InvalidRomanNumeral -} - -func romanToInt(input []byte) (int, bool) { - var output int - for _, n := range nums { - for bytes.HasPrefix(input, n.sym) { - output += n.val - input = input[len(n.sym):] - } - } - // if we are still left with input string values then the - // input was invalid and the bool is returned as false - return output, len(input) == 0 + // convert the integer to a roman numeral string and return it + return r3[input%1e4/1e3] + r2[input%1e3/1e2] + r1[input%100/10] + r0[input%10], nil } diff --git a/romannumeral_test.go b/romannumeral_test.go index 90d399f..258272d 100644 --- a/romannumeral_test.go +++ b/romannumeral_test.go @@ -1,4 +1,4 @@ -package romannumeral +package convertroman import ( "fmt" @@ -24,9 +24,9 @@ var testCases = map[string]int{ "MMCMXCIX": 2999, "MMM": 3000, "MMMCMLXXIX": 3979, "MMMCMXCIX": 3999, } -func TestIntToString(t *testing.T) { +func TestFromInt(t *testing.T) { for expected, input := range testCases { - out, err := IntToString(input) + out, err := FromInt(input) if err != nil { t.Errorf("IntToString(%d) returned an error %s", input, err.Error()) } @@ -34,157 +34,27 @@ func TestIntToString(t *testing.T) { t.Errorf("IntToString(%d) = %s; want %s", input, out, expected) } } - _, err := IntToString(100000) + _, err := FromInt(100000) if err == nil { t.Errorf("IntToString(%d) expected an error", 100000) } - _, err = IntToString(0) + _, err = FromInt(0) if err == nil { t.Errorf("IntToString(%d) expected an error", 0) } } -func TestIntToBytes(t *testing.T) { - for expected, input := range testCases { - out, err := IntToBytes(input) - if err != nil { - t.Errorf("IntToBytes(%d) returned an error %s", input, err.Error()) - } - if len(out) != len([]byte(expected)) { - t.Errorf("len(IntToBytes(%d)) = %d; want %d", input, len(out), len([]byte(expected))) - } - for i, char := range out { - if char != expected[i] { - t.Errorf("IntToBytes(%d) = %s; want %s", input, string(out), expected) - } - } - } - _, err := IntToBytes(100000) - if err == nil { - t.Errorf("IntToBytes(%d) expected an error", 100000) - } - _, err = IntToBytes(0) - if err == nil { - t.Errorf("IntToBytes(%d) expected an error", 0) - } -} - -func TestStringToInt(t *testing.T) { - for input, expected := range testCases { - out, err := StringToInt(input) - if err != nil { - t.Errorf("StringToInt(%s) returned an error %s", input, err.Error()) - } - if out != expected { - t.Errorf("StringToInt(%s) = %d; want %d", input, out, expected) - } - } - _, err := StringToInt("IVCMXCIX") - if err == nil { - t.Error("StringToInt(IVCMXCIX) expected an error") - } - - val, err := StringToInt("") - if val != 0 { - t.Errorf("StringToInt(\"\") = %d; want 0", val) - } - if err != nil { - t.Errorf("StringToInt(\"\") returned an error %s", err.Error()) - } -} - -func TestBytesToInt(t *testing.T) { - for input, expected := range testCases { - out, err := BytesToInt([]byte(input)) - if err != nil { - t.Errorf("StringToInt(%s) returned an error %s", input, err.Error()) - } - if out != expected { - t.Errorf("StringToInt(%s) = %d; want %d", input, out, expected) - } - } - _, err := BytesToInt([]byte("IVCMXCIX")) - if err == nil { - t.Error("BytesToInt(IVCMXCIX) expected an error") - } - - var in []byte - val, err := BytesToInt(in) - if val != 0 { - t.Errorf("BytesToInt(nil) = %d; want 0", val) - } - if err != nil { - t.Errorf("BytesToInt(nil) returned an error %s", err.Error()) - } - - in = []byte("") - val, err = BytesToInt(in) - if val != 0 { - t.Errorf("BytesToInt([]byte(\"\")) = %d; want 0", val) - } - if err != nil { - t.Errorf("BytesToInt([]byte(\"\")) returned an error %s", err.Error()) - } -} - -func BenchmarkIntToString(b *testing.B) { +func BenchmarkFromInt(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { - _, _ = IntToString(3999) + _, _ = FromInt(3999) } } -func BenchmarkIntToBytes(b *testing.B) { - b.ReportAllocs() - for i := 0; i < b.N; i++ { - _, _ = IntToBytes(3999) - } -} - -func BenchmarkStringToInt(b *testing.B) { - b.ReportAllocs() - for i := 0; i < b.N; i++ { - _, _ = StringToInt("MMMCMXCIX") - } -} - -func BenchmarkBytesToInt(b *testing.B) { - in := []byte("MMMCMXCIX") - b.ReportAllocs() - for i := 0; i < b.N; i++ { - _, _ = BytesToInt(in) - } -} - -func ExampleStringToInt() { - integer, err := StringToInt("IV") - if err != nil { - panic(err) - } - fmt.Println(integer == 4) // True -} - -func ExampleBytesToInt() { - input := []byte("IV") - integer, err := BytesToInt(input) - if err != nil { - panic(err) - } - fmt.Println(integer == 4) // True -} - -func ExampleIntToString() { - roman, err := IntToString(4) +func ExampleFromInt() { + roman, err := FromInt(4) if err != nil { panic(err) } fmt.Println(roman == "IV") // True } - -func ExampleIntToBytes() { - roman, err := IntToBytes(4) - if err != nil { - panic(err) - } - fmt.Println(string(roman) == "IV") // True -}