├── .github ├── dependabot.yml └── workflows │ ├── dependabot.yaml │ ├── security.yaml │ └── test.yaml ├── .gitignore ├── README.md ├── UNLICENSE ├── bucket.go ├── bucket_test.go ├── documentation.go ├── entry.go ├── entry_test.go ├── go.mod ├── go.sum ├── hamt.go ├── hamt_test.go ├── key_value.go ├── key_value_test.go ├── map.go ├── map_test.go ├── node.go ├── node_test.go ├── set.go ├── set_test.go └── test.go /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: / 5 | schedule: 6 | interval: daily 7 | - package-ecosystem: gomod 8 | directory: / 9 | schedule: 10 | interval: daily 11 | -------------------------------------------------------------------------------- /.github/workflows/dependabot.yaml: -------------------------------------------------------------------------------- 1 | name: dependabot 2 | on: pull_request 3 | permissions: 4 | contents: write 5 | pull-requests: write 6 | jobs: 7 | merge: 8 | runs-on: ubuntu-latest 9 | if: github.actor == 'dependabot[bot]' 10 | steps: 11 | - run: gh pr merge --auto --squash ${{ github.event.pull_request.html_url }} 12 | env: 13 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 14 | -------------------------------------------------------------------------------- /.github/workflows/security.yaml: -------------------------------------------------------------------------------- 1 | name: security 2 | on: 3 | push: 4 | branches: 5 | - main 6 | - v2 7 | pull_request: 8 | branches: 9 | - main 10 | - v2 11 | schedule: 12 | - cron: "0 0 * * *" 13 | jobs: 14 | codeql: 15 | runs-on: ubuntu-latest 16 | permissions: 17 | actions: read 18 | contents: read 19 | security-events: write 20 | steps: 21 | - uses: actions/checkout@v4 22 | - uses: github/codeql-action/init@v3 23 | - uses: github/codeql-action/autobuild@v3 24 | - uses: github/codeql-action/analyze@v3 25 | -------------------------------------------------------------------------------- /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | name: test 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | jobs: 8 | test: 9 | runs-on: ubuntu-latest 10 | container: 11 | image: golang 12 | steps: 13 | - uses: actions/checkout@v4 14 | - run: go build 15 | - uses: golangci/golangci-lint-action@v8 16 | - run: go test -covermode atomic -coverprofile coverage.txt 17 | - uses: codecov/codecov-action@v5 18 | - run: go test -bench . 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | coverage.txt 2 | .* 3 | !.gitignore 4 | !.github 5 | *.iml 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # hamt 2 | 3 | [![GitHub Action](https://img.shields.io/github/actions/workflow/status/raviqqe/hamt/test.yaml?branch=main&style=flat-square)](https://github.com/raviqqe/hamt/actions) 4 | [![Codecov](https://img.shields.io/codecov/c/github/raviqqe/hamt.svg?style=flat-square)](https://codecov.io/gh/raviqqe/hamt) 5 | [![Go Report Card](https://goreportcard.com/badge/github.com/raviqqe/hamt?style=flat-square)](https://goreportcard.com/report/github.com/raviqqe/hamt) 6 | [![License](https://img.shields.io/github/license/raviqqe/hamt.svg?style=flat-square)][unlicense] 7 | 8 | > For the old `any`-based API, refer to [the `main` branch](https://github.com/raviqqe/hamt/tree/main). 9 | 10 | Immutable and Memory Efficient Maps and Sets in Go. 11 | 12 | This package `hamt` provides immutable collection types of maps (associative arrays) 13 | and sets implemented as Hash-Array Mapped Tries (HAMTs). 14 | All operations of the collections, such as insert and delete, are immutable and 15 | create new ones keeping original ones unmodified. 16 | 17 | [Hash-Array Mapped Trie (HAMT)](https://en.wikipedia.org/wiki/Hash_array_mapped_trie) 18 | is a data structure popular as a map (a.k.a. associative array or dictionary) 19 | or set. 20 | Its immutable variant is adopted widely by functional programming languages 21 | like Scala and Clojure to implement immutable and memory-efficient associative 22 | arrays and sets. 23 | 24 | ## Installation 25 | 26 | ``` 27 | go get github.com/raviqqe/hamt/v2 28 | ``` 29 | 30 | ## Documentation 31 | 32 | [GoDoc](https://godoc.org/github.com/raviqqe/hamt/v2) 33 | 34 | ## Technical notes 35 | 36 | The implementation canonicalizes tree structures of HAMTs by eliminating 37 | intermediate nodes during delete operations as described 38 | in [the CHAMP paper][champ]. 39 | 40 | ## References 41 | 42 | - [Ideal Hash Trees](https://infoscience.epfl.ch/record/64398/files/idealhashtrees.pdf) 43 | - [Optimizing Hash-Array Mapped Tries for Fast and Lean Immutable JVM Collections][champ] 44 | 45 | ## License 46 | 47 | [The Unlicense][unlicense] 48 | 49 | [champ]: https://michael.steindorfer.name/publications/oopsla15.pdf 50 | [unlicense]: https://unlicense.org/ 51 | -------------------------------------------------------------------------------- /UNLICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /bucket.go: -------------------------------------------------------------------------------- 1 | package hamt 2 | 3 | type bucket[T Entry[T]] []T 4 | 5 | func newBucket[T Entry[T]]() bucket[T] { 6 | return bucket[T](nil) 7 | } 8 | 9 | func (b bucket[T]) Insert(e T) node[T] { 10 | for i, f := range b { 11 | if e.Equal(f) { 12 | new := make(bucket[T], len(b)) 13 | copy(new, b) 14 | new[i] = e 15 | return new 16 | } 17 | } 18 | 19 | return append(b, e) 20 | } 21 | 22 | func (b bucket[T]) Find(e T) *T { 23 | for _, f := range b { 24 | if e.Equal(f) { 25 | return &f 26 | } 27 | } 28 | 29 | return nil 30 | } 31 | 32 | func (b bucket[T]) Delete(e T) (node[T], bool) { 33 | for i, f := range b { 34 | if e.Equal(f) { 35 | return append(b[:i], b[i+1:]...), true 36 | } 37 | } 38 | 39 | return b, false 40 | } 41 | 42 | func (b bucket[T]) FirstRest() (*T, node[T]) { 43 | if len(b) == 0 { 44 | return nil, b 45 | } 46 | 47 | return &b[0], b[1:] 48 | } 49 | 50 | func (b bucket[T]) ForEach(cb func(T) error) error { 51 | for _, e := range b { 52 | if err := cb(e); err != nil { 53 | return err 54 | } 55 | } 56 | return nil 57 | } 58 | 59 | func (b bucket[T]) State() nodeState { 60 | switch len(b) { 61 | case 0: 62 | return empty 63 | case 1: 64 | return singleton 65 | } 66 | 67 | return more 68 | } 69 | 70 | func (b bucket[T]) Size() int { 71 | return len(b) 72 | } 73 | -------------------------------------------------------------------------------- /bucket_test.go: -------------------------------------------------------------------------------- 1 | package hamt 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestNewBucket(t *testing.T) { 10 | b := newBucket[entryInt]() 11 | 12 | assert.Equal(t, 0, b.Size()) 13 | } 14 | 15 | func TestBucketInsert(t *testing.T) { 16 | b := newBucket[entryInt]().Insert(entryInt(42)) 17 | 18 | assert.Equal(t, 1, b.Size()) 19 | assert.Equal(t, entryInt(42), *b.Find(entryInt(42))) 20 | 21 | b = b.Insert(entryInt(42)) 22 | 23 | assert.Equal(t, 1, b.Size()) 24 | assert.Equal(t, entryInt(42), *b.Find(entryInt(42))) 25 | 26 | b = b.Insert(entryInt(2049)) 27 | 28 | assert.Equal(t, 2, b.Size()) 29 | assert.Equal(t, entryInt(42), *b.Find(entryInt(42))) 30 | assert.Equal(t, entryInt(2049), *b.Find(entryInt(2049))) 31 | 32 | b = b.Insert(entryInt(2049)) 33 | 34 | assert.Equal(t, 2, b.Size()) 35 | assert.Equal(t, entryInt(42), *b.Find(entryInt(42))) 36 | assert.Equal(t, entryInt(2049), *b.Find(entryInt(2049))) 37 | } 38 | 39 | func TestBucketInsertAsMap(t *testing.T) { 40 | kv := newTestKeyValue(0, "foo") 41 | b := newBucket[keyValue[entryInt, string]]().Insert(kv) 42 | 43 | assert.Equal(t, 1, b.Size()) 44 | assert.EqualValues(t, kv, *b.Find(kv)) 45 | 46 | new := newTestKeyValue(0, "bar") 47 | b = b.Insert(new) 48 | 49 | assert.Equal(t, 1, b.Size()) 50 | assert.EqualValues(t, new, *b.Find(kv)) 51 | } 52 | 53 | func TestBucketDelete(t *testing.T) { 54 | b, changed := newBucket[entryInt]().Insert(entryInt(42)).Delete(entryInt(42)) 55 | 56 | assert.True(t, changed) 57 | assert.Equal(t, 0, b.Size()) 58 | assert.Nil(t, b.Find(entryInt(42))) 59 | } 60 | 61 | func TestBucketDeleteNonExistentEntries(t *testing.T) { 62 | b, changed := newBucket[entryInt]().Delete(entryInt(42)) 63 | 64 | assert.False(t, changed) 65 | assert.Equal(t, 0, b.Size()) 66 | 67 | b, changed = newBucket[entryInt]().Insert(entryInt(42)).Delete(entryInt(2049)) 68 | 69 | assert.False(t, changed) 70 | assert.Equal(t, 1, b.Size()) 71 | assert.Equal(t, entryInt(42), *b.Find(entryInt(42))) 72 | } 73 | 74 | func TestBucketFind(t *testing.T) { 75 | assert.Nil(t, newBucket[entryInt]().Find(entryInt(42))) 76 | } 77 | 78 | func TestBucketFirstRest(t *testing.T) { 79 | e, b := newBucket[entryInt]().FirstRest() 80 | 81 | assert.Nil(t, e) 82 | assert.Equal(t, 0, b.Size()) 83 | 84 | b = b.Insert(entryInt(42)) 85 | e, r := b.FirstRest() 86 | 87 | assert.Equal(t, entryInt(42), *e) 88 | assert.Equal(t, 0, r.Size()) 89 | 90 | b = b.Insert(entryInt(2049)) 91 | s := b.Size() 92 | 93 | for i := 0; i < s; i++ { 94 | e, b = b.FirstRest() 95 | 96 | assert.NotEqual(t, nil, e) 97 | assert.Equal(t, 1-i, b.Size()) 98 | } 99 | } 100 | 101 | func TestBucketState(t *testing.T) { 102 | var b node[entryInt] = newBucket[entryInt]() 103 | 104 | assert.Equal(t, empty, b.State()) 105 | 106 | b = b.Insert(entryInt(42)) 107 | 108 | assert.Equal(t, singleton, b.State()) 109 | 110 | b = b.Insert(entryInt(2049)) 111 | 112 | assert.Equal(t, more, b.State()) 113 | 114 | b = b.Insert(entryInt(0)) 115 | 116 | assert.Equal(t, more, b.State()) 117 | } 118 | -------------------------------------------------------------------------------- /documentation.go: -------------------------------------------------------------------------------- 1 | // Package hamt provides immutable collection types of maps (associative arrays) 2 | // and sets implemented as Hash-Array Mapped Tries (HAMTs). 3 | // All operations of the collections, such as insert and delete, are immutable 4 | // and create new ones keeping original ones unmodified. 5 | package hamt 6 | -------------------------------------------------------------------------------- /entry.go: -------------------------------------------------------------------------------- 1 | package hamt 2 | 3 | // Entry represents an entry in a collection, which can be compared to values of 4 | // type T (which is typically also the underlying type of the Entry). 5 | type Entry[T any] interface { 6 | Hash() uint32 7 | Equal(T) bool 8 | } 9 | -------------------------------------------------------------------------------- /entry_test.go: -------------------------------------------------------------------------------- 1 | package hamt 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | type entryInt uint32 10 | 11 | func (i entryInt) Hash() uint32 { 12 | return uint32(i) 13 | } 14 | 15 | func (i entryInt) Equal(j entryInt) bool { 16 | return i == j 17 | } 18 | 19 | func TestEntry(t *testing.T) { 20 | t.Log(Entry[entryInt](entryInt(42))) 21 | } 22 | 23 | func TestEntryKey(t *testing.T) { 24 | assert.Equal(t, uint32(42), entryInt(42).Hash()) 25 | } 26 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/raviqqe/hamt/v2 2 | 3 | go 1.18 4 | 5 | require github.com/stretchr/testify v1.10.0 6 | 7 | require ( 8 | github.com/davecgh/go-spew v1.1.1 // indirect 9 | github.com/pmezard/go-difflib v1.0.0 // indirect 10 | gopkg.in/yaml.v3 v3.0.1 // indirect 11 | ) 12 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 4 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 5 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 6 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 7 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 8 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 9 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 10 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 11 | -------------------------------------------------------------------------------- /hamt.go: -------------------------------------------------------------------------------- 1 | package hamt 2 | 3 | const arityBits = 5 4 | const arity = 32 5 | 6 | // hamt represents a HAMT data structure. 7 | type hamt[T Entry[T]] struct { 8 | level uint8 9 | children [arity]any 10 | } 11 | 12 | // newHamt creates a new HAMT. 13 | func newHamt[T Entry[T]](level uint8) hamt[T] { 14 | return hamt[T]{level: level} 15 | } 16 | 17 | // Insert inserts a value into a HAMT. 18 | func (h hamt[T]) Insert(e T) node[T] { 19 | i := h.calculateIndex(e) 20 | var c interface{} 21 | 22 | switch x := h.children[i].(type) { 23 | case nil: 24 | c = e 25 | case T: 26 | if x.Equal(e) { 27 | return h.setChild(i, e) 28 | } 29 | 30 | l := h.level + 1 31 | 32 | if l*arityBits > arity { 33 | c = newBucket[T]().Insert(x).Insert(e) 34 | } else { 35 | c = newHamt[T](l).Insert(x).Insert(e) 36 | } 37 | case node[T]: 38 | c = x.Insert(e) 39 | } 40 | 41 | return h.setChild(i, c) 42 | } 43 | 44 | // Delete deletes a value from a HAMT. 45 | func (h hamt[T]) Delete(e T) (node[T], bool) { 46 | i := h.calculateIndex(e) 47 | 48 | switch x := h.children[i].(type) { 49 | case T: 50 | if x.Equal(e) { 51 | return h.setChild(i, nil), true 52 | } 53 | case node[T]: 54 | n, b := x.Delete(e) 55 | 56 | if !b { 57 | return h, false 58 | } 59 | 60 | var c interface{} = n 61 | 62 | switch n.State() { 63 | case empty: 64 | panic("Invariant error: trees must be normalized.") 65 | case singleton: 66 | e, _ := n.FirstRest() 67 | c = *e 68 | } 69 | 70 | return h.setChild(i, c), true 71 | } 72 | 73 | return h, false 74 | } 75 | 76 | // Find finds a value in a HAMT. 77 | func (h hamt[T]) Find(e T) *T { 78 | switch x := h.children[h.calculateIndex(e)].(type) { 79 | case T: 80 | if x.Equal(e) { 81 | return &x 82 | } 83 | case node[T]: 84 | return x.Find(e) 85 | } 86 | 87 | return nil 88 | } 89 | 90 | // FirstRest returns a pointer to the first value and a HAMT without it. 91 | // If h is empty, the pointer will be nil. 92 | func (h hamt[T]) FirstRest() (*T, node[T]) { 93 | // Traverse entries and sub nodes separately for cache locality. 94 | for _, c := range h.children { 95 | if e, ok := c.(T); ok { 96 | h, _ := h.Delete(e) 97 | return &e, h 98 | } 99 | } 100 | 101 | for i, c := range h.children { 102 | if n, ok := c.(node[T]); ok { 103 | var e *T 104 | e, n = n.FirstRest() 105 | 106 | if e != nil { 107 | return e, h.setChild(i, n) 108 | } 109 | } 110 | } 111 | 112 | return nil, h // There is no entry inside. 113 | } 114 | 115 | func (h hamt[T]) ForEach(cb func(T) error) error { 116 | for _, child := range h.children { 117 | switch x := child.(type) { 118 | case nil: 119 | continue 120 | case T: 121 | if err := cb(x); err != nil { 122 | return err 123 | } 124 | case node[T]: 125 | if err := x.ForEach(cb); err != nil { 126 | return err 127 | } 128 | } 129 | } 130 | return nil 131 | } 132 | 133 | // State returns a state of a HAMT. 134 | func (h hamt[T]) State() nodeState { 135 | es := 0 136 | ns := 0 137 | 138 | for _, c := range h.children { 139 | switch c.(type) { 140 | case T: 141 | es++ 142 | case node[T]: 143 | ns++ 144 | } 145 | } 146 | 147 | if es+ns == 0 { 148 | return empty 149 | } else if es == 1 && ns == 0 { 150 | return singleton 151 | } 152 | 153 | return more 154 | } 155 | 156 | // Size returns a size of a HAMT. 157 | func (h hamt[T]) Size() int { 158 | s := 0 159 | 160 | for _, c := range h.children { 161 | switch x := c.(type) { 162 | case T: 163 | s++ 164 | case node[T]: 165 | s += x.Size() 166 | } 167 | } 168 | 169 | return s 170 | } 171 | 172 | func (h hamt[T]) calculateIndex(e T) int { 173 | return int((e.Hash() >> uint(arityBits*h.level)) % arity) 174 | } 175 | 176 | func (h hamt[T]) setChild(i int, c any) hamt[T] { 177 | g := h 178 | g.children[i] = c 179 | return g 180 | } 181 | -------------------------------------------------------------------------------- /hamt_test.go: -------------------------------------------------------------------------------- 1 | package hamt 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestNewHamt(t *testing.T) { 10 | assert.Equal(t, newHamt[entryInt](0).Size(), 0) 11 | } 12 | 13 | func TestHamtInsert(t *testing.T) { 14 | h := newHamt[entryInt](0).Insert(entryInt(42)) 15 | 16 | assert.Equal(t, 1, h.Size()) 17 | assert.Equal(t, entryInt(42), *h.Find(entryInt(42))) 18 | 19 | h = h.Insert(entryInt(2049)) 20 | 21 | assert.Equal(t, 2, h.Size()) 22 | assert.Equal(t, entryInt(42), *h.Find(entryInt(42))) 23 | assert.Equal(t, entryInt(2049), *h.Find(entryInt(2049))) 24 | } 25 | 26 | func TestHamtInsertAsMap(t *testing.T) { 27 | kv := newTestKeyValue(0, "foo") 28 | h := newHamt[keyValue[entryInt, string]](0).Insert(kv) 29 | 30 | assert.Equal(t, 1, h.Size()) 31 | assert.EqualValues(t, kv, *h.Find(kv)) 32 | 33 | new := newTestKeyValue(0, "bar") 34 | h = h.Insert(new) 35 | 36 | assert.Equal(t, 1, h.Size()) 37 | assert.EqualValues(t, new, *h.Find(kv)) 38 | } 39 | 40 | func TestHamtInsertWithBucketCreation(t *testing.T) { 41 | h := newHamt[entryInt](7).Insert(entryInt(0)).Insert(entryInt(0x80000000)) 42 | 43 | b, ok := h.(hamt[entryInt]).children[0].(bucket[entryInt]) 44 | 45 | assert.True(t, ok) 46 | assert.Equal(t, 2, b.Size()) 47 | } 48 | 49 | func TestHamtDelete(t *testing.T) { 50 | h := newHamt[entryInt](0).Insert(entryInt(42)) 51 | 52 | assert.Equal(t, 1, h.Size()) 53 | assert.Equal(t, entryInt(42), *h.Find(entryInt(42))) 54 | 55 | h, changed := h.Delete(entryInt(42)) 56 | 57 | assert.True(t, changed) 58 | assert.Equal(t, 0, h.Size()) 59 | assert.Nil(t, h.Find(entryInt(42))) 60 | } 61 | 62 | func TestHamtDeleteWithManyEntries(t *testing.T) { 63 | var h node[entryInt] = newHamt[entryInt](0) 64 | 65 | for i := 0; i < iterations; i++ { 66 | h = h.Insert(entryInt(uint32(i))) 67 | } 68 | 69 | assert.Equal(t, iterations, h.Size()) 70 | 71 | for i := 0; i < iterations; i++ { 72 | e := entryInt(uint32(i)) 73 | g, ok := h.Delete(e) 74 | 75 | assert.True(t, ok) 76 | assert.Nil(t, g.Find(e)) 77 | assert.Equal(t, h.Size()-1, g.Size()) 78 | 79 | h = g 80 | } 81 | 82 | assert.Equal(t, 0, h.Size()) 83 | } 84 | 85 | func TestHamtDeletePanicWithUnnormalizedTree(t *testing.T) { 86 | defer func() { 87 | assert.NotNil(t, recover()) 88 | }() 89 | 90 | e := entryInt(42) 91 | h := newHamt[entryInt](0) 92 | 93 | for i := range h.children { 94 | g := hamt[entryInt]{1, [32]any{}} 95 | 96 | if i == h.calculateIndex(e) { 97 | g.children[g.calculateIndex(e)] = e 98 | } 99 | 100 | h.children[i] = g 101 | } 102 | 103 | h.Delete(e) 104 | } 105 | 106 | func TestHamtFind(t *testing.T) { 107 | assert.Nil(t, newHamt[entryInt](0).Find(entryInt(42))) 108 | } 109 | 110 | func TestHamtFirstRest(t *testing.T) { 111 | var n node[entryInt] = newHamt[entryInt](0) 112 | e, m := n.FirstRest() 113 | 114 | assert.Nil(t, e) 115 | assert.Equal(t, 0, m.Size()) 116 | 117 | n = n.Insert(entryInt(42)) 118 | e, m = n.FirstRest() 119 | 120 | assert.Equal(t, entryInt(42), *e) 121 | assert.Equal(t, 0, m.Size()) 122 | 123 | n = n.Insert(entryInt(2049)) 124 | s := n.Size() 125 | 126 | for i := 0; i < s; i++ { 127 | e, n = n.FirstRest() 128 | 129 | assert.NotEqual(t, nil, e) 130 | assert.Equal(t, 1-i, n.Size()) 131 | } 132 | } 133 | 134 | func TestHamtFirstRestWithManyEntries(t *testing.T) { 135 | var h node[entryInt] = newHamt[entryInt](0) 136 | 137 | for i := 0; i < iterations; i++ { 138 | h = h.Insert(entryInt(uint32(i))) 139 | } 140 | 141 | assert.Equal(t, iterations, h.Size()) 142 | 143 | for i := 0; i < iterations; i++ { 144 | e, g := h.FirstRest() 145 | 146 | assert.NotNil(t, e) 147 | assert.Equal(t, h.Size()-1, g.Size()) 148 | 149 | h = g 150 | } 151 | 152 | assert.Equal(t, 0, h.Size()) 153 | } 154 | 155 | func TestHamtForEach(t *testing.T) { 156 | var n node[entryInt] = newHamt[entryInt](0) 157 | err := n.ForEach(func(entry entryInt) error { 158 | assert.Fail(t, "for-each callback called on empty hamt") 159 | return nil 160 | }) 161 | assert.NoError(t, err) 162 | 163 | n = n.Insert(entryInt(42)) 164 | entries := make([]entryInt, 0) 165 | err = n.ForEach(func(entry entryInt) error { 166 | entries = append(entries, entry) 167 | return nil 168 | }) 169 | assert.NoError(t, err) 170 | assert.Equal(t, []entryInt{42}, entries) 171 | 172 | n = n.Insert(entryInt(2049)) 173 | entries = make([]entryInt, 0) 174 | err = n.ForEach(func(entry entryInt) error { 175 | entries = append(entries, entry) 176 | return nil 177 | }) 178 | assert.NoError(t, err) 179 | assert.ElementsMatch(t, []entryInt{42, 2049}, entries) 180 | } 181 | 182 | func TestHamtForEachWithManyEntries(t *testing.T) { 183 | var h node[entryInt] = newHamt[entryInt](0) 184 | 185 | want := make([]entryInt, 0) 186 | for i := 0; i < iterations; i++ { 187 | e := entryInt(uint32(i)) 188 | h = h.Insert(e) 189 | want = append(want, e) 190 | } 191 | 192 | assert.Equal(t, iterations, h.Size()) 193 | 194 | entries := make([]entryInt, 0) 195 | err := h.ForEach(func(entry entryInt) error { 196 | entries = append(entries, entry) 197 | return nil 198 | }) 199 | assert.NoError(t, err) 200 | assert.Len(t, entries, iterations) 201 | assert.ElementsMatch(t, want, entries) 202 | } 203 | 204 | func TestHamtState(t *testing.T) { 205 | var h node[entryInt] = newHamt[entryInt](0) 206 | 207 | assert.Equal(t, empty, h.State()) 208 | 209 | h = h.Insert(entryInt(42)) 210 | 211 | assert.Equal(t, singleton, h.State()) 212 | 213 | h = h.Insert(entryInt(2049)) 214 | 215 | assert.Equal(t, more, h.State()) 216 | 217 | h = h.Insert(entryInt(0)) 218 | 219 | assert.Equal(t, more, h.State()) 220 | } 221 | 222 | func TestHamtSize(t *testing.T) { 223 | assert.Equal(t, 0, newHamt[entryInt](0).Size()) 224 | } 225 | 226 | func TestHamtCalculateIndex(t *testing.T) { 227 | e := entryInt(0xffffffff) 228 | 229 | for i := 0; i < 6; i++ { 230 | assert.Equal(t, 0x1f, newHamt[entryInt](uint8(i)).calculateIndex(e)) 231 | } 232 | 233 | assert.Equal(t, 3, newHamt[entryInt](6).calculateIndex(e)) 234 | } 235 | 236 | func TestArity(t *testing.T) { 237 | assert.Equal(t, arity, int(1< 0 { 281 | _, hh = hh.FirstRest() 282 | } 283 | } 284 | } 285 | 286 | func BenchmarkHamtForEachIteration(b *testing.B) { 287 | var h node[entryInt] = newHamt[entryInt](0) 288 | for i := 0; i < iterations; i++ { 289 | h = h.Insert(entryInt(i)) 290 | } 291 | 292 | b.ResetTimer() 293 | 294 | for i := 0; i < b.N; i++ { 295 | _ = h.ForEach(func(entry entryInt) error { 296 | return nil 297 | }) 298 | } 299 | } 300 | 301 | func BenchmarkBuiltinMapForEach(b *testing.B) { 302 | m := make(map[entryInt]struct{}) 303 | for i := 0; i < iterations; i++ { 304 | m[entryInt(i)] = struct{}{} 305 | } 306 | 307 | b.ResetTimer() 308 | 309 | for i := 0; i < b.N; i++ { 310 | for e := range m { 311 | _ = e 312 | } 313 | } 314 | } 315 | 316 | func BenchmarkBuiltinSliceForEach(b *testing.B) { 317 | m := make([]entryInt, 0) 318 | for i := 0; i < iterations; i++ { 319 | m = append(m, entryInt(i)) 320 | } 321 | 322 | b.ResetTimer() 323 | 324 | for i := 0; i < b.N; i++ { 325 | for _, e := range m { 326 | _ = e 327 | } 328 | } 329 | } 330 | -------------------------------------------------------------------------------- /key_value.go: -------------------------------------------------------------------------------- 1 | package hamt 2 | 3 | type keyValue[K Entry[K], V any] struct { 4 | key K 5 | value V 6 | } 7 | 8 | func newKeyValue[K Entry[K], V any](k K, v V) keyValue[K, V] { 9 | return keyValue[K, V]{k, v} 10 | } 11 | 12 | func (kv keyValue[K, V]) Hash() uint32 { 13 | return kv.key.Hash() 14 | } 15 | 16 | func (kv keyValue[K, V]) Equal(e keyValue[K, V]) bool { 17 | return kv.key.Equal(e.key) 18 | } 19 | -------------------------------------------------------------------------------- /key_value_test.go: -------------------------------------------------------------------------------- 1 | package hamt 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func newTestKeyValue(k uint32, v string) keyValue[entryInt, string] { 10 | return newKeyValue(entryInt(k), v) 11 | } 12 | 13 | func TestNewKeyValue(t *testing.T) { 14 | newKeyValue(entryInt(42), "value") 15 | } 16 | 17 | func TestKeyValueAsEntry(t *testing.T) { 18 | t.Log(Entry[keyValue[entryInt, string]](newKeyValue(entryInt(42), "value"))) 19 | } 20 | 21 | func TestKeyValueHash(t *testing.T) { 22 | assert.Equal(t, uint32(42), newKeyValue(entryInt(42), "value").Hash()) 23 | } 24 | 25 | func TestKeyValueEqual(t *testing.T) { 26 | k := entryInt(42) 27 | kv := newKeyValue(k, "value") 28 | 29 | assert.True(t, kv.Equal(kv)) 30 | assert.True(t, kv.key.Equal(k)) 31 | assert.False(t, kv.Equal(newKeyValue(entryInt(2049), "value"))) 32 | assert.False(t, kv.key.Equal(entryInt(2049))) 33 | } 34 | -------------------------------------------------------------------------------- /map.go: -------------------------------------------------------------------------------- 1 | package hamt 2 | 3 | // Map represents a map (associative array). 4 | type Map[K Entry[K], V any] struct { 5 | set Set[keyValue[K, V]] 6 | } 7 | 8 | // NewMap creates a new map. 9 | func NewMap[K Entry[K], V any]() Map[K, V] { 10 | return Map[K, V]{NewSet[keyValue[K, V]]()} 11 | } 12 | 13 | // Insert inserts a key-value pair into a map. 14 | func (m Map[K, V]) Insert(k K, v V) Map[K, V] { 15 | return Map[K, V]{m.set.Insert(newKeyValue(k, v))} 16 | } 17 | 18 | // Delete deletes a pair of a key and a value from a map. 19 | func (m Map[K, V]) Delete(k K) Map[K, V] { 20 | var zero V 21 | return Map[K, V]{m.set.Delete(newKeyValue(k, zero))} 22 | } 23 | 24 | // Find finds a value corresponding to a given key from a map. 25 | // It returns nil if no value is found. 26 | func (m Map[K, V]) Find(k K) *V { 27 | var zero V 28 | e := m.set.find(newKeyValue(k, zero)) 29 | 30 | if e == nil { 31 | return nil 32 | } 33 | 34 | return &e.value 35 | } 36 | 37 | // Include returns true if a key-value pair corresponding with a given key is 38 | // included in a map, or false otherwise. 39 | func (m Map[K, V]) Include(k K) bool { 40 | return m.Find(k) != nil 41 | } 42 | 43 | // FirstRest returns a key-value pair in a map and a rest of the map. 44 | // This method is useful for iteration. 45 | // The key and value would be nil if the map is empty. 46 | func (m Map[K, V]) FirstRest() (*K, *V, Map[K, V]) { 47 | e, s := m.set.FirstRest() 48 | m = Map[K, V]{s} 49 | 50 | if e == nil { 51 | return nil, nil, m 52 | } 53 | 54 | return &e.key, &e.value, m 55 | } 56 | 57 | func (m Map[K, V]) ForEach(cb func(K, V) error) error { 58 | return m.set.ForEach(func(kv keyValue[K, V]) error { 59 | return cb(kv.key, kv.value) 60 | }) 61 | } 62 | 63 | // Merge merges 2 maps into one. 64 | func (m Map[K, V]) Merge(n Map[K, V]) Map[K, V] { 65 | return Map[K, V]{m.set.Merge(n.set)} 66 | } 67 | 68 | // Size returns a size of a map. 69 | func (m Map[K, V]) Size() int { 70 | return m.set.Size() 71 | } 72 | -------------------------------------------------------------------------------- /map_test.go: -------------------------------------------------------------------------------- 1 | package hamt 2 | 3 | import ( 4 | "math/rand" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestNewMap(t *testing.T) { 11 | NewMap[entryInt, string]() 12 | } 13 | 14 | func TestMapInsert(t *testing.T) { 15 | m := NewMap[entryInt, string]() 16 | 17 | for i := 0; i < iterations; i++ { 18 | e := entryInt(rand.Int31()) 19 | m = m.Insert(e, "value") 20 | assert.Equal(t, "value", *m.Find(e)) 21 | } 22 | } 23 | 24 | func TestMapOperations(t *testing.T) { 25 | m := NewMap[entryInt, string]() 26 | 27 | for i := 0; i < iterations; i++ { 28 | k := entryInt(rand.Int31() % 256) 29 | var mm Map[entryInt, string] 30 | 31 | if rand.Int()%2 == 0 { 32 | mm = m.Insert(k, "value") 33 | 34 | assert.Equal(t, "value", *mm.Find(k)) 35 | 36 | if m.Include(k) { 37 | assert.Equal(t, m.Size(), mm.Size()) 38 | } else { 39 | assert.Equal(t, m.Size()+1, mm.Size()) 40 | } 41 | } else { 42 | mm = m.Delete(k) 43 | 44 | assert.Nil(t, mm.Find(k)) 45 | 46 | if m.Include(k) { 47 | assert.Equal(t, m.Size()-1, mm.Size()) 48 | } else { 49 | assert.Equal(t, m.Size(), mm.Size()) 50 | } 51 | } 52 | 53 | m = mm 54 | } 55 | } 56 | 57 | func TestMapFirstRest(t *testing.T) { 58 | m := NewMap[entryInt, string]() 59 | k, v, mm := m.FirstRest() 60 | 61 | assert.Nil(t, k) 62 | assert.Nil(t, v) 63 | assert.Equal(t, 0, mm.Size()) 64 | 65 | m = m.Insert(entryInt(42), "value") 66 | k, v, mm = m.FirstRest() 67 | 68 | assert.Equal(t, entryInt(42), *k) 69 | assert.Equal(t, "value", *v) 70 | assert.Equal(t, 0, mm.Size()) 71 | 72 | m = m.Insert(entryInt(2049), "value") 73 | s := m.Size() 74 | 75 | for i := 0; i < s; i++ { 76 | k, v, m = m.FirstRest() 77 | 78 | assert.NotNil(t, k) 79 | assert.Equal(t, "value", *v) 80 | assert.Equal(t, 1-i, m.Size()) 81 | } 82 | } 83 | 84 | func TestMapForEach(t *testing.T) { 85 | m := NewMap[entryInt, string]() 86 | err := m.ForEach(func(key entryInt, val string) error { 87 | assert.Fail(t, "for-each callback called on empty set") 88 | return nil 89 | }) 90 | assert.NoError(t, err) 91 | 92 | m = m.Insert(entryInt(42), "value") 93 | kvs := make([]keyValue[entryInt, string], 0) 94 | want := []keyValue[entryInt, string]{ 95 | { 96 | key: entryInt(42), 97 | value: "value", 98 | }, 99 | } 100 | err = m.ForEach(func(key entryInt, val string) error { 101 | kvs = append(kvs, keyValue[entryInt, string]{ 102 | key: key, 103 | value: val, 104 | }) 105 | return nil 106 | }) 107 | assert.NoError(t, err) 108 | assert.Equal(t, want, kvs) 109 | 110 | m = m.Insert(entryInt(2049), "value2") 111 | kvs = make([]keyValue[entryInt, string], 0) 112 | want = []keyValue[entryInt, string]{ 113 | { 114 | key: entryInt(42), 115 | value: "value", 116 | }, 117 | { 118 | key: entryInt(2049), 119 | value: "value2", 120 | }, 121 | } 122 | err = m.ForEach(func(key entryInt, val string) error { 123 | kvs = append(kvs, keyValue[entryInt, string]{ 124 | key: key, 125 | value: val, 126 | }) 127 | return nil 128 | }) 129 | assert.NoError(t, err) 130 | assert.ElementsMatch(t, want, kvs) 131 | } 132 | 133 | func TestMapMerge(t *testing.T) { 134 | for _, ms := range [][3]Map[entryInt, string]{ 135 | { 136 | NewMap[entryInt, string](), 137 | NewMap[entryInt, string](), 138 | NewMap[entryInt, string](), 139 | }, 140 | { 141 | NewMap[entryInt, string]().Insert(entryInt(1), "foo"), 142 | NewMap[entryInt, string](), 143 | NewMap[entryInt, string]().Insert(entryInt(1), "foo"), 144 | }, 145 | { 146 | NewMap[entryInt, string](), 147 | NewMap[entryInt, string]().Insert(entryInt(1), "foo"), 148 | NewMap[entryInt, string]().Insert(entryInt(1), "foo"), 149 | }, 150 | { 151 | NewMap[entryInt, string]().Insert(entryInt(2), "foo"), 152 | NewMap[entryInt, string]().Insert(entryInt(1), "foo"), 153 | NewMap[entryInt, string]().Insert(entryInt(1), "foo").Insert(entryInt(2), "foo")}, 154 | } { 155 | assert.Equal(t, ms[2], ms[0].Merge(ms[1])) 156 | } 157 | } 158 | 159 | func TestMapSize(t *testing.T) { 160 | assert.Equal(t, 0, NewMap[entryInt, string]().Size()) 161 | } 162 | -------------------------------------------------------------------------------- /node.go: -------------------------------------------------------------------------------- 1 | package hamt 2 | 3 | type nodeState int8 4 | 5 | const ( 6 | empty nodeState = iota 7 | singleton 8 | more 9 | ) 10 | 11 | // node represents a node in a HAMT. 12 | type node[T Entry[T]] interface { 13 | Insert(T) node[T] 14 | Delete(T) (node[T], bool) 15 | Find(T) *T 16 | FirstRest() (*T, node[T]) 17 | ForEach(func(T) error) error 18 | State() nodeState 19 | Size() int // for debugging 20 | } 21 | -------------------------------------------------------------------------------- /node_test.go: -------------------------------------------------------------------------------- 1 | package hamt 2 | 3 | import "testing" 4 | 5 | func TestHamtAsNode(t *testing.T) { 6 | t.Log(node[entryInt](newHamt[entryInt](0))) 7 | } 8 | 9 | func TestBucketAsNode(t *testing.T) { 10 | t.Log(node[entryInt](newBucket[entryInt]())) 11 | } 12 | -------------------------------------------------------------------------------- /set.go: -------------------------------------------------------------------------------- 1 | package hamt 2 | 3 | // Set represents a set. 4 | type Set[T Entry[T]] struct { 5 | size int 6 | hamt hamt[T] 7 | } 8 | 9 | // NewSet creates a new set. 10 | func NewSet[T Entry[T]]() Set[T] { 11 | return Set[T]{0, newHamt[T](0)} 12 | } 13 | 14 | // Insert inserts a value into a set. 15 | func (s Set[T]) Insert(e T) Set[T] { 16 | size := s.size 17 | 18 | if s.find(e) == nil { 19 | size++ 20 | } 21 | 22 | return Set[T]{size, s.hamt.Insert(e).(hamt[T])} 23 | } 24 | 25 | // Delete deletes a value from a set. 26 | func (s Set[T]) Delete(e T) Set[T] { 27 | n, b := s.hamt.Delete(e) 28 | size := s.size 29 | 30 | if b { 31 | size-- 32 | } 33 | 34 | return Set[T]{size, n.(hamt[T])} 35 | } 36 | 37 | func (s Set[T]) find(e T) *T { 38 | return s.hamt.Find(e) 39 | } 40 | 41 | // Include returns true if a given entry is included in a set, or false otherwise. 42 | func (s Set[T]) Include(e T) bool { 43 | return s.find(e) != nil 44 | } 45 | 46 | // FirstRest returns a pointer to a value in a set and a rest of the set. 47 | // This method is useful for iteration. 48 | func (s Set[T]) FirstRest() (*T, Set[T]) { 49 | e, n := s.hamt.FirstRest() 50 | size := s.size 51 | 52 | if e != nil { 53 | size-- 54 | } 55 | 56 | return e, Set[T]{size, n.(hamt[T])} 57 | } 58 | 59 | func (s Set[T]) ForEach(cb func(T) error) error { 60 | return s.hamt.ForEach(cb) 61 | } 62 | 63 | // Merge merges 2 sets into one. 64 | func (s Set[T]) Merge(t Set[T]) Set[T] { 65 | for t.Size() != 0 { 66 | var e *T 67 | e, t = t.FirstRest() 68 | s = s.Insert(*e) 69 | } 70 | 71 | return s 72 | } 73 | 74 | // Size returns a size of a set. 75 | func (s Set[T]) Size() int { 76 | return s.size 77 | } 78 | -------------------------------------------------------------------------------- /set_test.go: -------------------------------------------------------------------------------- 1 | package hamt 2 | 3 | import ( 4 | "math/rand" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestNewSet(t *testing.T) { 11 | assert.Equal(t, NewSet[entryInt]().Size(), 0) 12 | } 13 | 14 | func TestSetInsert(t *testing.T) { 15 | s := NewSet[entryInt]() 16 | 17 | for i := 0; i < iterations; i++ { 18 | e := entryInt(rand.Int31()) 19 | s = s.Insert(e) 20 | assert.True(t, s.Include(e)) 21 | } 22 | } 23 | 24 | func TestSetOperations(t *testing.T) { 25 | s := NewSet[entryInt]() 26 | 27 | for i := 0; i < iterations; i++ { 28 | assert.Equal(t, s.hamt.Size(), s.Size()) 29 | 30 | e := entryInt(rand.Int31() % 256) 31 | var ss Set[entryInt] 32 | 33 | if rand.Int()%2 == 0 { 34 | ss = s.Insert(e) 35 | 36 | assert.True(t, ss.Include(e)) 37 | 38 | if s.Include(e) { 39 | assert.Equal(t, s.Size(), ss.Size()) 40 | } else { 41 | assert.Equal(t, s.Size()+1, ss.Size()) 42 | } 43 | } else { 44 | ss = s.Delete(e) 45 | 46 | assert.False(t, ss.Include(e)) 47 | 48 | if s.Include(e) { 49 | assert.Equal(t, s.Size()-1, ss.Size()) 50 | } else { 51 | assert.Equal(t, s.Size(), ss.Size()) 52 | } 53 | } 54 | 55 | s = ss 56 | } 57 | } 58 | 59 | func TestSetFirstRest(t *testing.T) { 60 | s := NewSet[entryInt]() 61 | e, ss := s.FirstRest() 62 | 63 | assert.Nil(t, e) 64 | assert.Equal(t, 0, ss.Size()) 65 | 66 | s = s.Insert(entryInt(42)) 67 | e, ss = s.FirstRest() 68 | 69 | assert.Equal(t, entryInt(42), *e) 70 | assert.Equal(t, 0, ss.Size()) 71 | 72 | s = s.Insert(entryInt(2049)) 73 | size := s.Size() 74 | 75 | for i := 0; i < size; i++ { 76 | e, s = s.FirstRest() 77 | 78 | assert.NotEqual(t, nil, e) 79 | assert.Equal(t, 1-i, s.Size()) 80 | } 81 | } 82 | 83 | func TestSetForEach(t *testing.T) { 84 | s := NewSet[entryInt]() 85 | err := s.ForEach(func(entry entryInt) error { 86 | assert.Fail(t, "for-each callback called on empty set") 87 | return nil 88 | }) 89 | assert.NoError(t, err) 90 | 91 | s = s.Insert(entryInt(42)) 92 | entries := make([]entryInt, 0) 93 | err = s.ForEach(func(entry entryInt) error { 94 | entries = append(entries, entry) 95 | return nil 96 | }) 97 | assert.NoError(t, err) 98 | assert.Equal(t, []entryInt{42}, entries) 99 | 100 | s = s.Insert(entryInt(2049)) 101 | entries = make([]entryInt, 0) 102 | err = s.ForEach(func(entry entryInt) error { 103 | entries = append(entries, entry) 104 | return nil 105 | }) 106 | assert.NoError(t, err) 107 | assert.ElementsMatch(t, []entryInt{42, 2049}, entries) 108 | } 109 | 110 | func TestSetMerge(t *testing.T) { 111 | for _, ss := range [][3]Set[entryInt]{ 112 | { 113 | NewSet[entryInt](), 114 | NewSet[entryInt](), 115 | NewSet[entryInt](), 116 | }, 117 | { 118 | NewSet[entryInt]().Insert(entryInt(1)), 119 | NewSet[entryInt](), 120 | NewSet[entryInt]().Insert(entryInt(1)), 121 | }, 122 | { 123 | NewSet[entryInt](), 124 | NewSet[entryInt]().Insert(entryInt(1)), 125 | NewSet[entryInt]().Insert(entryInt(1)), 126 | }, 127 | { 128 | NewSet[entryInt]().Insert(entryInt(2)), 129 | NewSet[entryInt]().Insert(entryInt(1)), 130 | NewSet[entryInt]().Insert(entryInt(1)).Insert(entryInt(2))}, 131 | } { 132 | assert.Equal(t, ss[2], ss[0].Merge(ss[1])) 133 | } 134 | } 135 | 136 | func TestSetSize(t *testing.T) { 137 | assert.Equal(t, 0, NewSet[entryInt]().Size()) 138 | } 139 | 140 | func BenchmarkSetInsert(b *testing.B) { 141 | s := NewSet[entryInt]() 142 | 143 | b.ResetTimer() 144 | 145 | for i := 0; i < b.N; i++ { 146 | for i := 0; i < iterations; i++ { 147 | s = s.Insert(entryInt(i)) 148 | } 149 | } 150 | } 151 | 152 | func BenchmarkSetSize(b *testing.B) { 153 | s := NewSet[entryInt]() 154 | 155 | b.ResetTimer() 156 | 157 | for i := 0; i < b.N; i++ { 158 | for i := 0; i < iterations; i++ { 159 | s = s.Insert(entryInt(i)) 160 | b.Log(s.Size()) 161 | } 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /test.go: -------------------------------------------------------------------------------- 1 | package hamt 2 | 3 | const iterations = 10000 // nolint: deadcode 4 | --------------------------------------------------------------------------------