diff --git a/Go/shortUrl.go b/Go/shortUrl.go new file mode 100644 index 0000000..bf6c28b --- /dev/null +++ b/Go/shortUrl.go @@ -0,0 +1,58 @@ +/** + * ShortURL: Bijective conversion between natural numbers (IDs) and short strings + * Licensed under the MIT License (https://opensource.org/licenses/MIT) + * + * ShortURL::encode() takes an ID and turns it into a short string + * ShortURL::decode() takes a short string and turns it into an ID + * + * Features: + * + large alphabet (51 chars) and thus very short resulting strings + * + proof against offensive words (removed 'a', 'e', 'i', 'o' and 'u') + * + unambiguous (removed 'I', 'l', '1', 'O' and '0') + **/ + +package shorturl + +import ( + "fmt" + "strings" +) + +const ( + // Alphabets is "set of allowed alphabets" + Alphabets = "23456789bcdfghjkmnpqrstvwxyzBCDFGHJKLMNPQRSTVWXYZ-_" + // Base is const size of alphabets string + Base = len(Alphabets) +) + +//Reverse string assuming that its all runes. +func Reverse(s string) string { + runes := []rune(s) + for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 { + runes[i], runes[j] = runes[j], runes[i] + } + return string(runes) +} + +// Encode Given a generated number, get the URL back +func Encode(n int) string { + sb := strings.Builder{} + for n > 0 { + sb.WriteByte(Alphabets[n%Base]) + n = n / Base + } + return Reverse(sb.String()) +} + +// Decode Given a URL(path), the decoder decodes it to a unique number. +func Decode(path string) (int, error) { + n := 0 + for _, c := range path { + index := strings.IndexRune(Alphabets, c) + if index < 0 { + return 0, fmt.Errorf("Invalid character %c in input %s", c, path) + } + n = n*Base + index + } + return n, nil +} diff --git a/Go/shortUrl_test.go b/Go/shortUrl_test.go new file mode 100644 index 0000000..899adc8 --- /dev/null +++ b/Go/shortUrl_test.go @@ -0,0 +1,44 @@ +package shorturl + +import ( + "fmt" + "testing" +) + +//go test -run Encode +func TestEncodeDecode(t *testing.T) { + path := []string{ + "tvwxyzBF2", + "2BCDFGHJP", + "pgK8p", + "", + } + for _, v := range path { + i, e := Decode(v) + if e != nil { + t.Fail() + } + s := Encode(i) + fmt.Println(i, "<==>", s) + if v != s { + if v != string(Alphabets[0])+s { // v may start with Alphabet[0], which in base51 can mean 0. + t.Fail() + fmt.Println("expected :", v, "Got: ", s) + } + } + } +} + +// BenchmarkEncodeOriginal run command : go test -bench=Original +func BenchmarkEncodeOriginal(t *testing.B) { + for i := 0; i < 10000000; i++ { + Encode(i) + } +} + +// BenchmarkDecode run command; go test -bench=Decode +func BenchmarkDecode(t *testing.B) { + for i := 0; i < 10000000; i++ { + Decode("BCDFGHJP") + } +}