Strip unneeded functionality; slightly optimize for specific use-case

This commit is contained in:
2024-10-25 15:22:53 -04:00
parent 1823dc2593
commit f3fdc8d9e8
6 changed files with 37 additions and 307 deletions
-36
View File
@@ -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
+13 -41
View File
@@ -1,25 +1,21 @@
# Go Roman Numerals # Go Roman Numerals
[![Go Reference](https://pkg.go.dev/badge/github.com/brandenc40/romannumeral.svg)](https://pkg.go.dev/github.com/brandenc40/romannumeral) This package was adapted from [romannumeral](https://github.com/brandenc40/romannumeral) to fit my specific minimal use-case.
[![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.
A reliable module using the most efficient methods possible for converting between All functionality except for the ability to convert integers to roman numerals has been removed.
roman numerals and integers in Go. Algorithms adopted from [here](https://rosettacode.org/wiki/Roman_numerals).
If you need any additional functionality or further documentation, please see the original package.
### Benchmark Results ### Benchmark Results
```sh ```sh
goos: darwin goos: linux
goarch: arm64 goarch: amd64
pkg: github.com/brandenc40/romannumeral pkg: github.com/rwinkhart/convertroman
BenchmarkIntToString-8 56474846 20.84 ns/op 0 B/op 0 allocs/op cpu: AMD Ryzen 9 3900X 12-Core Processor
BenchmarkIntToBytes-8 48157634 24.36 ns/op 0 B/op 0 allocs/op BenchmarkFromInt-24 62046414 21.08 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
PASS PASS
ok github.com/brandenc40/romannumeral 6.111s ok github.com/rwinkhart/convertroman 1.332s
``` ```
### Example ### Example
@@ -29,38 +25,14 @@ package main
import ( import (
"fmt" "fmt"
rom "github.com/brandenc40/romannumeral" rom "github.com/rwinkhart/convertroman"
) )
func ExampleStringToInt() { func ExampleFromInt() {
integer, err := rom.StringToInt("IV") roman, err := rom.FromInt(4)
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)
if err != nil { if err != nil {
panic(err) panic(err)
} }
fmt.Println(roman == "IV") // True fmt.Println(roman == "IV") // True
} }
func ExampleIntToBytes() {
roman, err := rom.IntToBytes(4)
if err != nil {
panic(err)
}
fmt.Println(string(roman) == "IV") // True
}
``` ```
+1 -1
View File
@@ -1,3 +1,3 @@
module github.com/brandenc40/romannumeral module github.com/rwinkhart/convertroman
go 1.15 go 1.15
View File
+14 -90
View File
@@ -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 // 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: // numbers outside of that range. See here for details on viniculum:
// https://en.wikipedia.org/wiki/Roman_numerals#Large_numbers // https://en.wikipedia.org/wiki/Roman_numerals#Large_numbers
package romannumeral
import ( import (
"bytes"
"errors" "errors"
) )
// numeral describes the value and symbol of a single roman numeral
type numeral struct {
val int
sym []byte
}
var ( 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 - 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") 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. // 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 // method from here: https://rosettacode.org/wiki/Roman_numerals/Encode#Go
r0 = []string{"", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX"} r0 = [10]string{"", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX"}
r1 = []string{"", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC"} r1 = [10]string{"", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC"}
r2 = []string{"", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM"} r2 = [10]string{"", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM"}
r3 = []string{"", "M", "MM", "MMM"} r3 = [4]string{"", "M", "MM", "MMM"}
) )
// IntToString converts an integer value to a roman numeral string. An error is // FromInt converts an integer value to a roman numeral string.
// returned if the integer is not between 1 and 3999. // An error is returned if the integer is not between 1 and 3999.
func IntToString(input int) (string, error) { func FromInt(input int) (string, error) {
if outOfBounds(input) { // ensure provided integer is within the valid range
return "", IntegerOutOfBounds 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 // convert the integer to a roman numeral string and return it
// returned if the integer is not between 1 and 3999. return r3[input%1e4/1e3] + r2[input%1e3/1e2] + r1[input%100/10] + r0[input%10], nil
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
} }
+9 -139
View File
@@ -1,4 +1,4 @@
package romannumeral package convertroman
import ( import (
"fmt" "fmt"
@@ -24,9 +24,9 @@ var testCases = map[string]int{
"MMCMXCIX": 2999, "MMM": 3000, "MMMCMLXXIX": 3979, "MMMCMXCIX": 3999, "MMCMXCIX": 2999, "MMM": 3000, "MMMCMLXXIX": 3979, "MMMCMXCIX": 3999,
} }
func TestIntToString(t *testing.T) { func TestFromInt(t *testing.T) {
for expected, input := range testCases { for expected, input := range testCases {
out, err := IntToString(input) out, err := FromInt(input)
if err != nil { if err != nil {
t.Errorf("IntToString(%d) returned an error %s", input, err.Error()) 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) t.Errorf("IntToString(%d) = %s; want %s", input, out, expected)
} }
} }
_, err := IntToString(100000) _, err := FromInt(100000)
if err == nil { if err == nil {
t.Errorf("IntToString(%d) expected an error", 100000) t.Errorf("IntToString(%d) expected an error", 100000)
} }
_, err = IntToString(0) _, err = FromInt(0)
if err == nil { if err == nil {
t.Errorf("IntToString(%d) expected an error", 0) t.Errorf("IntToString(%d) expected an error", 0)
} }
} }
func TestIntToBytes(t *testing.T) { func BenchmarkFromInt(b *testing.B) {
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) {
b.ReportAllocs() b.ReportAllocs()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
_, _ = IntToString(3999) _, _ = FromInt(3999)
} }
} }
func BenchmarkIntToBytes(b *testing.B) { func ExampleFromInt() {
b.ReportAllocs() roman, err := FromInt(4)
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)
if err != nil { if err != nil {
panic(err) panic(err)
} }
fmt.Println(roman == "IV") // True fmt.Println(roman == "IV") // True
} }
func ExampleIntToBytes() {
roman, err := IntToBytes(4)
if err != nil {
panic(err)
}
fmt.Println(string(roman) == "IV") // True
}