-
Notifications
You must be signed in to change notification settings - Fork 3
/
alpha.go
142 lines (118 loc) · 3.53 KB
/
alpha.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
package alpha
import (
"math/big"
"strconv"
"unicode"
"github.com/pkg/errors"
)
var (
dictionary = []byte{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'}
base *big.Int
dictMap map[byte]*big.Int
)
func init() {
base = big.NewInt(int64(len(dictionary)))
dictMap = make(map[byte]*big.Int)
j := 0
for _, val := range dictionary {
dictMap[val] = big.NewInt(int64(j))
j = j + 1
}
}
//checks if given string is a valid numeric
func isValidNumeric(s string) bool {
for _, r := range s {
if !unicode.IsNumber(r) {
return false
}
}
return true
}
// isAsciiPrintable checks if s is ascii and printable, aka doesn't include tab, backspace, etc.
func isAsciiPrintable(s string) bool {
for _, r := range s {
if r > unicode.MaxASCII || !unicode.IsPrint(r) || unicode.IsPunct(r) {
return false
}
}
return true
}
// encodeInt encodes a big.Int integer, the value of remaining is changed to 0 during the process
func encodeInt(remaining *big.Int) (string, error) {
var result []byte
var index int
var strVal string
a := big.NewInt(0)
b := big.NewInt(0)
c := big.NewInt(0)
d := big.NewInt(0)
exponent := 1
for remaining.Cmp(big.NewInt(0)) != 0 {
a.Exp(base, big.NewInt(int64(exponent)), nil) //16^1 = 16
b = b.Mod(remaining, a) //119 % 16 = 7 | 112 % 256 = 112
c = c.Exp(base, big.NewInt(int64(exponent-1)), nil)
d = d.Div(b, c)
//if d > dictionary.length, we have a problem. but BigInteger doesnt have
//a greater than method :-( hope for the best. theoretically, d is always
//an index of the dictionary!
strVal = d.String()
index, _ = strconv.Atoi(strVal)
result = append(result, dictionary[index])
remaining = remaining.Sub(remaining, b) //119 - 7 = 112 | 112 - 112 = 0
exponent = exponent + 1
}
//need to reverse it, since the start of the list contains the least significant values
return string(reverse(result)), nil
}
// EncodeInt encodes a big.Int integer
func EncodeInt(i *big.Int) (string, error) {
remaining := big.NewInt(0)
remaining.Set(i)
return encodeInt(remaining)
}
// Encode converts the big integer to alpha id (an alphanumeric id with mixed cases)
func Encode(s string) (string, error) {
//numeric validation
if !isValidNumeric(s) {
return "", errors.New("Encode string is not a valid numeric")
}
remaining := big.NewInt(0)
remaining.SetString(s, 10)
return encodeInt(remaining)
}
// DecodeInt converts the alpha id to a bit.Int
func DecodeInt(s string) (*big.Int, error) {
//Validate if given string is valid
if !isAsciiPrintable(s) {
return nil, errors.New("Decode string is not valid.[a-z, A_Z, 0-9] only allowed")
}
//reverse it, coz its already reversed!
chars2 := reverse([]byte(s))
bi := big.NewInt(0)
exponent := 0
a := big.NewInt(0)
b := big.NewInt(0)
intermed := big.NewInt(0)
for _, c := range chars2 {
a = dictMap[c]
intermed = intermed.Exp(base, big.NewInt(int64(exponent)), nil)
b = b.Mul(intermed, a)
bi = bi.Add(bi, b)
exponent = exponent + 1
}
return bi, nil
}
// Decode converts the alpha id to big integer
func Decode(s string) (string, error) {
bi, err := DecodeInt(s)
if err != nil {
return "", err
}
return bi.String(), nil
}
func reverse(bs []byte) []byte {
for i, j := 0, len(bs)-1; i < j; i, j = i+1, j-1 {
bs[i], bs[j] = bs[j], bs[i]
}
return bs
}