Skip to content

Commit

Permalink
Added zero padding option
Browse files Browse the repository at this point in the history
  • Loading branch information
Bhargav committed Jul 15, 2023
1 parent 2c8c16d commit 4b0add9
Show file tree
Hide file tree
Showing 8 changed files with 223 additions and 42 deletions.
5 changes: 2 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
## 0.2.0+1
## 0.2.1

- Added `.golangci.yaml`
- Improved test coverage
- Added zero padding option

## 0.2.0

Expand Down
11 changes: 8 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,14 @@ hexadecimal, _ := hexadecimalToBase58Converter.Inverse().Convert("GjWGF6jERR9ymr

#### Decimal (emoji) ↔ Hexadecimal
```go
decimalEmojiToHexadecimalConverter, _ := baseconv.NewBaseConversion("0️⃣1️⃣2️⃣3️⃣4️⃣5️⃣6️⃣7️⃣8️⃣9️⃣", baseconv.Base16)
decimalEmojiToHexadecimalConverter, _ := baseconv.NewBaseConversion(
"0️⃣1️⃣2️⃣3️⃣4️⃣5️⃣6️⃣7️⃣8️⃣9️⃣", baseconv.Base16,
options.BaseConversion().
SetZeroPadding(true),
)

hexadecimal, _ := decimalEmojiToHexadecimalConverter.Convert("5️⃣1️⃣9️⃣6️⃣6️⃣")
// hexadecimal == "CAFE"
// hexadecimal == "0CAFE"

decimalEmoji, _ := decimalEmojiToHexadecimalConverter.Inverse().Convert("DEADC0DE")
// decimalEmoji == "3️⃣7️⃣3️⃣5️⃣9️⃣2️⃣9️⃣0️⃣5️⃣4️⃣"
Expand All @@ -55,12 +59,13 @@ decimalEmoji, _ := decimalEmojiToHexadecimalConverter.Inverse().Convert("DEADC0D
- `Base32hex` - `0123456789ABCDEFGHIJKLMNOPQRSTUV`
- `Base36` - `0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ`
- `Base58` - `123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz`
- `Base62` - `0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz`
- `Base64` - `ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/`
- `Base64url` - `ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_`

## Syntax

### *`NewBaseConversion(from string, to string) (*baseConversion, error)`*
### *`NewBaseConversion(from string, to string, opts ...*options.BaseConversionOptions) (*baseConversion, error)`*

*`from`* - String of numeral symbols representing the digits of `from` numeral system.

Expand Down
18 changes: 13 additions & 5 deletions alphabet.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package baseconv // import "go.dkinom.dev/baseconv"
package baseconv

import (
"fmt"
Expand All @@ -8,8 +8,9 @@ import (
)

type alphabet struct {
characters []string
characterSet map[string]int
characters []string
characterSet map[string]int
zeroCharacter string
}

func NewAlphabet(str string) (*alphabet, error) {
Expand All @@ -30,9 +31,13 @@ func NewAlphabet(str string) (*alphabet, error) {
return nil, fmt.Errorf("Must not have duplicate characters in alphabet")
}

a := alphabet{characters, characterSet}
a := &alphabet{
characters: characters,
characterSet: characterSet,
zeroCharacter: characters[0],
}

return &a, nil
return a, nil
}

func (a *alphabet) String() string {
Expand Down Expand Up @@ -126,6 +131,9 @@ const Base36 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
// Base58 numeral system
const Base58 = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"

// Base62 numeral system
const Base62 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"

// Base64 numeral system
const Base64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"

Expand Down
77 changes: 64 additions & 13 deletions base_conversion.go
Original file line number Diff line number Diff line change
@@ -1,20 +1,27 @@
package baseconv // import "go.dkinom.dev/baseconv"
package baseconv

import (
"fmt"
"math"
"strings"

"github.com/rivo/uniseg"
"go.dkinom.dev/baseconv/options"
)

// Provides ability to convert numbers between different
// positional numeral systems
type baseConversion struct {
fromAlphabet alphabet
toAlphabet alphabet

lengthFactor float64

zeroPadding bool
}

// Initializes baseConversion struct with the given from and to alphabets
func NewBaseConversion(from string, to string) (*baseConversion, error) {
func NewBaseConversion(from string, to string, opts ...*options.BaseConversionOptions) (*baseConversion, error) {
fromAlphabet, err := NewAlphabet(from)
if err != nil {
return nil, err
Expand All @@ -24,9 +31,39 @@ func NewBaseConversion(from string, to string) (*baseConversion, error) {
return nil, err
}

b := baseConversion{*fromAlphabet, *toAlphabet}
b := NewBaseConversionAlphabet(*fromAlphabet, *toAlphabet, opts...)

return &b, nil
return b, nil
}

// Initializes baseConversion struct with the given from and to alphabets
func NewBaseConversionAlphabet(from alphabet, to alphabet, opts ...*options.BaseConversionOptions) *baseConversion {
b := &baseConversion{fromAlphabet: from, toAlphabet: to}

b.lengthFactor = math.Log(float64(from.Radix())) / math.Log(float64(to.Radix()))

opt := options.MergeBaseConversionOptions(opts...)

b.configure(opt)

return b
}

func (b *baseConversion) configure(opt *options.BaseConversionOptions) {
// Zero padding
b.zeroPadding = false
if opt.ZeroPadding != nil {
b.zeroPadding = *opt.ZeroPadding
}
}

func (b *baseConversion) options() *options.BaseConversionOptions {
o := options.BaseConversion()

// Zero padding
o.SetZeroPadding(b.zeroPadding)

return o
}

// Returns numeral representation of s in toAlphabet
Expand All @@ -40,13 +77,12 @@ func (b *baseConversion) Convert(s string) (r string, err error) {
return
}

// Horner's method
func (b *baseConversion) convertIntegralPart(ip string) string {
fromBase := b.fromAlphabet.Radix()
toBase := b.toAlphabet.Radix()

var changeBase func(_values []int) string
changeBase = func(_values []int) string {
var changeBase func(_values []int) (string, int)
changeBase = func(_values []int) (string, int) { // Horner's method
values := []int{}
for i, v := range _values {
if v != 0 {
Expand All @@ -56,7 +92,7 @@ func (b *baseConversion) convertIntegralPart(ip string) string {
}

if len(values) == 0 {
return ""
return "", 0
}

remainder := 0
Expand All @@ -68,7 +104,9 @@ func (b *baseConversion) convertIntegralPart(ip string) string {
remainder = remainder % toBase
}

return changeBase(quotients) + b.toAlphabet.characters[remainder]
r, rLen := changeBase(quotients)

return r + b.toAlphabet.characters[remainder], rLen + 1
}

values := []int{}
Expand All @@ -78,11 +116,24 @@ func (b *baseConversion) convertIntegralPart(ip string) string {

values = append(values, b.fromAlphabet.characterSet[character])
}
ipLen := len(values)

r, rLen := changeBase(values)
if rLen == 0 {
r, rLen = b.toAlphabet.zeroCharacter, 1
}

r := changeBase(values)
if b.zeroPadding {
currentLength := rLen
wantedLength := int(math.Ceil(float64(ipLen) * b.lengthFactor))

if len(r) == 0 {
return b.toAlphabet.characters[0]
if currentLength < wantedLength {
var sb strings.Builder
sb.WriteString(strings.Repeat(b.toAlphabet.zeroCharacter, wantedLength-currentLength))
sb.WriteString(r)

r = sb.String()
}
}

return r
Expand All @@ -91,5 +142,5 @@ func (b *baseConversion) convertIntegralPart(ip string) string {
// Returns a baseConversion which converts numerals
// from toAlphabet to fromAlphabet
func (b *baseConversion) Inverse() *baseConversion {
return &baseConversion{b.toAlphabet, b.fromAlphabet}
return NewBaseConversionAlphabet(b.toAlphabet, b.fromAlphabet, b.options())
}
114 changes: 98 additions & 16 deletions base_conversion_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package baseconv
import (
"fmt"
"testing"

"go.dkinom.dev/baseconv/options"
)

func TestValidations(t *testing.T) {
Expand Down Expand Up @@ -63,6 +65,58 @@ func TestConverter(t *testing.T) {
}
testConverter(t, decimalToBinary)
}
{
b, err := NewBaseConversion(
Decimal, Binary,
nil, options.BaseConversion().SetZeroPadding(true), nil,
)
if err != nil {
t.Fatal(err)
}
var decimalToBinary = map[string]interface{}{
"instance": b,
"testCases": []map[string]string{
{"from": "0", "to": "0000"},
{"from": "1", "to": "0001"},
{"from": "2", "to": "0010"},
{"from": "3", "to": "0011"},
{"from": "4", "to": "0100"},
{"from": "5", "to": "0101"},
{"from": "6", "to": "0110"},
{"from": "7", "to": "0111"},
{"from": "8", "to": "1000"},
{"from": "9", "to": "1001"},
{"from": "10", "to": "0001010"},
},
}
testConverter(t, decimalToBinary)
}
{
b, err := NewBaseConversion(
Decimal, Binary,
options.BaseConversion().SetZeroPadding(true),
)
if err != nil {
t.Fatal(err)
}
var decimalToBinary = map[string]interface{}{
"instance": b.Inverse(),
"testCases": []map[string]string{
{"to": "00", "from": "0000"},
{"to": "01", "from": "0001"},
{"to": "02", "from": "0010"},
{"to": "03", "from": "0011"},
{"to": "04", "from": "0100"},
{"to": "05", "from": "0101"},
{"to": "06", "from": "0110"},
{"to": "07", "from": "0111"},
{"to": "08", "from": "1000"},
{"to": "09", "from": "1001"},
{"to": "010", "from": "0001010"},
},
}
testConverter(t, decimalToBinary)
}
{
b, err := NewBaseConversion(Decimal, "0️⃣1️⃣2️⃣3️⃣4️⃣5️⃣6️⃣7️⃣8️⃣9️⃣")
if err != nil {
Expand All @@ -86,10 +140,53 @@ func TestConverter(t *testing.T) {
}
testConverter(t, decimalToDecimalEmoji)
}
{
b, err := NewBaseConversion(
Decimal, "0️⃣1️⃣2️⃣3️⃣4️⃣5️⃣6️⃣7️⃣8️⃣9️⃣",
nil, options.BaseConversion().SetZeroPadding(true), nil,
)
if err != nil {
t.Fatal(err)
}
var decimalToDecimalEmoji = map[string]interface{}{
"instance": b,
"testCases": []map[string]string{
{"from": "0", "to": "0️⃣"},
{"from": "1", "to": "1️⃣"},
{"from": "2", "to": "2️⃣"},
{"from": "3", "to": "3️⃣"},
{"from": "4", "to": "4️⃣"},
{"from": "5", "to": "5️⃣"},
{"from": "6", "to": "6️⃣"},
{"from": "7", "to": "7️⃣"},
{"from": "8", "to": "8️⃣"},
{"from": "9", "to": "9️⃣"},
{"from": "10", "to": "1️⃣0️⃣"},
},
}
testConverter(t, decimalToDecimalEmoji)
}
{
b, err := NewBaseConversion(
Hexadecimal, "0️⃣1️⃣2️⃣3️⃣4️⃣5️⃣6️⃣7️⃣8️⃣9️⃣",
options.BaseConversion().SetZeroPadding(true),
)
if err != nil {
t.Fatal(err)
}
var decimalToDecimalEmoji = map[string]interface{}{
"instance": b.Inverse(),
"testCases": []map[string]string{
{"to": "0CAFE", "from": "5️⃣1️⃣9️⃣6️⃣6️⃣"},
{"to": "0DEADC0DE", "from": "3️⃣7️⃣3️⃣5️⃣9️⃣2️⃣9️⃣0️⃣5️⃣4️⃣"},
},
}
testConverter(t, decimalToDecimalEmoji)
}
}

func testConverter(t *testing.T, converter map[string]interface{}) {
t.Run("function", func(t *testing.T) {
t.Run("Convert", func(t *testing.T) {
for _, testCase := range converter["testCases"].([]map[string]string) {
t.Run(fmt.Sprintf("%v -> %v", testCase["from"], testCase["to"]), func(t *testing.T) {
actual, err := (converter["instance"]).(*baseConversion).Convert(testCase["from"])
Expand All @@ -103,19 +200,4 @@ func testConverter(t *testing.T, converter map[string]interface{}) {
})
}
})

t.Run("inverse function", func(t *testing.T) {
for _, testCase := range converter["testCases"].([]map[string]string) {
t.Run(fmt.Sprintf("%v -> %v", testCase["to"], testCase["from"]), func(t *testing.T) {
actual, err := (converter["instance"]).(*baseConversion).Inverse().Convert(testCase["to"])
if err != nil {
t.Fatal(err)
}
expected := testCase["from"]
if actual != expected {
t.Fatal()
}
})
}
})
}
1 change: 1 addition & 0 deletions doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package baseconv // import "go.dkinom.dev/baseconv"
Loading

0 comments on commit 4b0add9

Please sign in to comment.