Skip to content

Commit

Permalink
Merge pull request #59 from anatoly-kussul/custom-len-alphabet
Browse files Browse the repository at this point in the history
Support for custom character length alphabets
  • Loading branch information
lithammer authored Dec 2, 2024
2 parents d3d4af7 + 730191d commit 9c03ed0
Show file tree
Hide file tree
Showing 9 changed files with 334 additions and 142 deletions.
7 changes: 4 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ jobs:
strategy:
matrix:
platform: [ubuntu-latest, macos-latest, windows-latest]
go-version: ['1.16', '1.17']
go-version: ['1.21']
runs-on: ${{ matrix.platform }}
steps:
- name: Setup Go
Expand All @@ -21,14 +21,15 @@ jobs:
uses: actions/checkout@v4

- name: Download Go dependencies
run: go mod download
run: go mod download -x
env:
GOPROXY: "https://proxy.golang.org"

- name: Lint
uses: golangci/[email protected]
with:
version: v1.44
version: v1.62.2
args: --timeout 3m --verbose

- name: Build
run: go build -v .
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,9 @@ instead of `New()`.
shortuuid.NewWithNamespace("http://example.com")
```

It's possible to use a custom alphabet as well, though it has to be 57
characters long.
It's possible to use a custom alphabet as well (at least 2
characters long).
It will automatically sort and remove duplicates from your alphabet to ensure consistency

```go
alphabet := "23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxy="
Expand Down
67 changes: 35 additions & 32 deletions alphabet.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,44 @@ package shortuuid

import (
"fmt"
"sort"
"strings"
"math"
"slices"
)

// DefaultAlphabet is the default alphabet used.
const DefaultAlphabet = "23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
const (
DefaultAlphabet = "23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
rune1Max = 1<<7 - 1
)

type alphabet struct {
chars [57]rune
len int64
chars []rune
len int64
encLen int64
singleBytes bool
}

// Remove duplicates and sort it to ensure reproducability.
// Remove duplicates and sort it to ensure reproducibility.
func newAlphabet(s string) alphabet {
abc := dedupe(strings.Split(s, ""))
abc := []rune(s)
slices.Sort(abc)
abc = slices.Compact(abc)

if len(abc) != 57 {
panic("encoding alphabet is not 57-bytes long")
if len(abc) < 2 {
panic("encoding alphabet must be at least two characters")
}

sort.Strings(abc)
a := alphabet{
len: int64(len(abc)),
chars: abc,
len: int64(len(abc)),
encLen: int64(math.Ceil(128 / math.Log2(float64(len(abc))))),
singleBytes: true,
}

for i, char := range strings.Join(abc, "") {
a.chars[i] = char
for _, c := range a.chars {
if c > rune1Max {
a.singleBytes = false
break
}
}

return a
Expand All @@ -41,25 +52,17 @@ func (a *alphabet) Length() int64 {
// Index returns the index of the first instance of t in the alphabet, or an
// error if t is not present.
func (a *alphabet) Index(t rune) (int64, error) {
for i, char := range a.chars {
if char == t {
return int64(i), nil
i, j := 0, int(a.len)
for i < j {
h := int(uint(i+j) >> 1)
if a.chars[h] < t {
i = h + 1
} else {
j = h
}
}
return 0, fmt.Errorf("element '%v' is not part of the alphabet", t)
}

// dudupe removes duplicate characters from s.
func dedupe(s []string) []string {
var out []string
m := make(map[string]bool)

for _, char := range s {
if _, ok := m[char]; !ok {
m[char] = true
out = append(out, char)
}
if i >= int(a.len) || a.chars[i] != t {
return 0, fmt.Errorf("element '%v' is not part of the alphabet", t)
}

return out
return int64(i), nil
}
17 changes: 0 additions & 17 deletions alphabet_test.go
Original file line number Diff line number Diff line change
@@ -1,26 +1,9 @@
package shortuuid

import (
"strings"
"testing"
)

func TestDedupe(t *testing.T) {
tests := []struct {
in, out string
}{
{"01010101010101", "01"},
{"abcabcfoo", "abcfo"},
}

for _, test := range tests {
in := strings.Join(dedupe(strings.Split(test.in, "")), "")
if in != test.out {
t.Errorf("expected %q, got %q", in, test.out)
}
}
}

func TestAlphabetIndex(t *testing.T) {
abc := newAlphabet(DefaultAlphabet)
idx, err := abc.Index('z')
Expand Down
82 changes: 0 additions & 82 deletions base57.go

This file was deleted.

Loading

0 comments on commit 9c03ed0

Please sign in to comment.