v0.0.295 added generic base-conversion algorithm
All checks were successful
Build Docker and Deploy / Run goext test-suite (push) Successful in 1m4s

This commit is contained in:
Mike Schwörer 2023-11-01 00:23:17 +01:00
parent 75f71fe3db
commit 2fbd5cf965
Signed by: Mikescher
GPG Key ID: D3C7172E0A70F8CF
4 changed files with 274 additions and 2 deletions

View File

@ -1,5 +1,5 @@
package goext
const GoextVersion = "0.0.294"
const GoextVersion = "0.0.295"
const GoextVersionTimestamp = "2023-10-31T22:58:28+0100"
const GoextVersionTimestamp = "2023-11-01T00:23:17+0100"

178
langext/baseAny.go Normal file
View File

@ -0,0 +1,178 @@
package langext
import (
"crypto/rand"
"errors"
"math"
"math/big"
)
type AnyBaseConverter struct {
base uint64
charset []rune
}
func NewAnyBaseConverter(cs string) AnyBaseConverter {
rcs := []rune(cs)
return AnyBaseConverter{
base: uint64(len(rcs)),
charset: rcs,
}
}
func (bc AnyBaseConverter) Rand(rlen int) string {
biBase := big.NewInt(int64(bc.base))
randMax := big.NewInt(math.MaxInt64)
r := ""
for i := 0; i < rlen; i++ {
v, err := rand.Int(rand.Reader, randMax)
if err != nil {
panic(err)
}
r += string(bc.charset[v.Mod(v, biBase).Int64()])
}
return r
}
func (bc AnyBaseConverter) EncodeUInt64(num uint64) string {
if num == 0 {
return "0"
}
b := ""
// loop as long the num is bigger than zero
for num > 0 {
r := num % bc.base
num -= r
num /= base62Base
b += string(bc.charset[int(r)])
}
return b
}
func (bc AnyBaseConverter) DecodeUInt64(str string) (uint64, error) {
if str == "" {
return 0, errors.New("empty string")
}
result := uint64(0)
for _, v := range str {
result *= base62Base
pos := ArrFirstIndex(bc.charset, v)
if pos == -1 {
return 0, errors.New("invalid character: " + string(v))
}
result += uint64(pos)
}
return result, nil
}
func (bc AnyBaseConverter) Encode(src []byte) string {
value := new(big.Int)
value.SetBytes(src)
return bc.EncodeBigInt(value)
}
func (bc AnyBaseConverter) EncodeBigInt(src *big.Int) string {
value := new(big.Int)
value.Set(src)
isneg := value.Sign() < 0
answer := ""
if isneg {
value.Neg(value)
}
biBase := big.NewInt(int64(bc.base))
rem := new(big.Int)
for value.Sign() > 0 {
value.QuoRem(value, biBase, rem)
answer = string(bc.charset[rem.Int64()]) + answer
}
if isneg {
return "-" + answer
} else {
return answer
}
}
func (bc AnyBaseConverter) Decode(src string) ([]byte, error) {
value, err := bc.DecodeToBigInt(src)
if err != nil {
return nil, err
}
return value.Bytes(), nil
}
func (bc AnyBaseConverter) DecodeToBigInt(_src string) (*big.Int, error) {
result := new(big.Int)
result.SetInt64(0)
src := []rune(_src)
if len(src) == 0 {
return nil, errors.New("string is empty")
}
if bc.base < 2 {
return nil, errors.New("not enough digits")
}
i := 0
sign := new(big.Int)
sign.SetInt64(1)
if src[i] == '+' {
i++
} else if src[i] == '-' {
i++
sign.SetInt64(-1)
}
if i >= len(src) {
return nil, errors.New("no digits in input")
}
biBase := big.NewInt(int64(bc.base))
oldResult := new(big.Int)
for ; i < len(src); i++ {
n := ArrFirstIndex(bc.charset, src[i])
if n < 0 {
return nil, errors.New("invalid characters in input")
}
oldResult.Set(result)
result.Mul(result, biBase)
result.Add(result, big.NewInt(int64(n)))
if result.Cmp(oldResult) < 0 {
return nil, errors.New("overflow")
}
}
if sign.Cmp(big.NewInt(0)) < 0 {
result.Neg(result)
}
return result, nil
}

80
langext/baseAny_test.go Normal file
View File

@ -0,0 +1,80 @@
package langext
import (
"gogs.mikescher.com/BlackForestBytes/goext/tst"
"testing"
)
func _anyEncStr(bc AnyBaseConverter, v string) string {
vr := bc.Encode([]byte(v))
return vr
}
func _anyDecStr(bc AnyBaseConverter, v string) string {
vr, err := bc.Decode(v)
if err != nil {
panic(err)
}
return string(vr)
}
func TestAnyBase58DefaultEncoding(t *testing.T) {
tst.AssertEqual(t, _anyEncStr(NewAnyBaseConverter("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"), "Hello"), "9Ajdvzr")
tst.AssertEqual(t, _anyEncStr(NewAnyBaseConverter("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"), "If debugging is the process of removing software bugs, then programming must be the process of putting them in."), "48638SMcJuah5okqPx4kCVf5d8QAdgbdNf28g7ReY13prUENNbMyssjq5GjsrJHF5zeZfqs4uJMUJHr7VbrU4XBUZ2Fw9DVtqtn9N1eXucEWSEZahXV6w4ysGSWqGdpeYTJf1MdDzTg8vfcQViifJjZX")
}
func TestAnyBase58DefaultDecoding(t *testing.T) {
tst.AssertEqual(t, _anyDecStr(NewAnyBaseConverter("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"), "9Ajdvzr"), "Hello")
tst.AssertEqual(t, _anyDecStr(NewAnyBaseConverter("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"), "48638SMcJuah5okqPx4kCVf5d8QAdgbdNf28g7ReY13prUENNbMyssjq5GjsrJHF5zeZfqs4uJMUJHr7VbrU4XBUZ2Fw9DVtqtn9N1eXucEWSEZahXV6w4ysGSWqGdpeYTJf1MdDzTg8vfcQViifJjZX"), "If debugging is the process of removing software bugs, then programming must be the process of putting them in.")
}
func TestAnyBaseDecode(t *testing.T) {
const (
Binary = "01"
Decimal = "0123456789"
Hex = "0123456789ABCDEF"
DNA = "ACGT"
Base32 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"
Base58 = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
Base62 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
Base64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
Base256 = "🚀🪐☄🛰🌌🌑🌒🌓🌔🌕🌖🌗🌘🌍🌏🌎🐉☀💻🖥💾💿😂❤😍🤣😊🙏💕😭😘👍😅👏😁🔥🥰💔💖💙😢🤔😆🙄💪😉☺👌🤗💜😔😎😇🌹🤦🎉💞✌✨🤷😱😌🌸🙌😋💗💚😏💛🙂💓🤩😄😀🖤😃💯🙈👇🎶😒🤭❣😜💋👀😪😑💥🙋😞😩😡🤪👊🥳😥🤤👉💃😳✋😚😝😴🌟😬🙃🍀🌷😻😓⭐✅🥺🌈😈🤘💦✔😣🏃💐☹🎊💘😠☝😕🌺🎂🌻😐🖕💝🙊😹🗣💫💀👑🎵🤞😛🔴😤🌼😫⚽🤙☕🏆🤫👈😮🙆🍻🍃🐶💁😲🌿🧡🎁⚡🌞🎈❌✊👋😰🤨😶🤝🚶💰🍓💢🤟🙁🚨💨🤬✈🎀🍺🤓😙💟🌱😖👶🥴▶➡❓💎💸⬇😨🌚🦋😷🕺⚠🙅😟😵👎🤲🤠🤧📌🔵💅🧐🐾🍒😗🤑🌊🤯🐷☎💧😯💆👆🎤🙇🍑❄🌴💣🐸💌📍🥀🤢👅💡💩👐📸👻🤐🤮🎼🥵🚩🍎🍊👼💍📣🥂"
)
type TestDef struct {
FromCS string
FromVal string
ToCS string
ToVal string
}
defs := []TestDef{
{Binary, "10100101011100000101010", Decimal, "5421098"},
{Decimal, "5421098", DNA, "CCAGGTGAAGGG"},
{Decimal, "5421098", DNA, "CCAGGTGAAGGG"},
{Decimal, "80085", Base256, "🪐💞🔵"},
{Hex, "48656C6C6C20576F526C5421", Base64, "SGVsbGwgV29SbFQh"},
{Base64, "SGVsbGw/gV29SbF+Qh", Base32, "CIMVWGY3B7QFO32SNRPZBB"},
{Base64, "SGVsbGw/gV29SbF+Qh", Base58, "2fUsGKQUcgQcwSqpvy6"},
{Base64, "SGVsbGw/gV29SbF+Qh", Base62, "V34nvybdQ3m3RHk9Sr"},
}
for _, def := range defs {
d1 := NewAnyBaseConverter(def.FromCS)
d2 := NewAnyBaseConverter(def.ToCS)
v1 := tst.Must(d1.Decode(def.FromVal))(t)
v2 := tst.Must(d2.Decode(def.ToVal))(t)
tst.AssertArrayEqual(t, v1, v2)
str2 := d2.Encode(v1)
tst.AssertEqual(t, str2, def.ToVal)
str1 := d1.Encode(v2)
tst.AssertEqual(t, str1, def.FromVal)
}
}

View File

@ -14,6 +14,20 @@ func AssertEqual[T comparable](t *testing.T, actual T, expected T) {
}
}
func AssertArrayEqual[T comparable](t *testing.T, actual []T, expected []T) {
t.Helper()
if len(actual) != len(expected) {
t.Errorf("values differ: Actual: '%v', Expected: '%v' (len %d <> %d)", actual, expected, len(actual), len(expected))
return
}
for i := 0; i < len(actual); i++ {
if actual[i] != expected[i] {
t.Errorf("values differ: Actual: '%v', Expected: '%v' (at index %d)", actual, expected, i)
return
}
}
}
func AssertNotEqual[T comparable](t *testing.T, actual T, expected T) {
t.Helper()
if actual == expected {