Repository: tidwall/hashmap Branch: master Commit: e128c7c0599b Files: 9 Total size: 30.3 KB Directory structure: gitextract_w3ied2c9/ ├── BENCH.md ├── LICENSE ├── README.md ├── go.mod ├── go.sum ├── map.go ├── map_test.go ├── set.go └── set_test.go ================================================ FILE CONTENTS ================================================ ================================================ FILE: BENCH.md ================================================ # Performance While this implementation was designed with performance in mind, it's not necessarily better, faster, smarter, or sexier than the built-in Go hashmap. Here's a very rough comparison. The following benchmarks were run on a Linux Desktop (3.8 GHz 8-Core AMD Ryzen 7 5800X) using Go version 1.19. The key types are either strings or ints and the values are always ints. In all cases the maps start from zero capacity, like: ```go m := make(map[string]int) // go stdlib var m hashmap.Map[string, int] // this package ``` ```shell MAPBENCH=100000 go test ``` ## 100,000 random string keys ```shell ## STRING KEYS -- tidwall -- set 100,000 ops 15ms 6,535,956/sec get 100,000 ops 7ms 15,009,648/sec reset 100,000 ops 5ms 20,811,745/sec scan 20 ops 8ms 2,539/sec delete 100,000 ops 7ms 14,557,933/sec memory 4,194,288 bytes 41/entry -- stdlib -- set 100,000 ops 17ms 5,892,223/sec get 100,000 ops 8ms 12,148,359/sec reset 100,000 ops 4ms 24,779,419/sec scan 20 ops 14ms 1,395/sec delete 100,000 ops 8ms 11,915,708/sec memory 3,966,288 bytes 39/entry ``` ## 100,000 random int keys ```shell ## INT KEYS -- tidwall -- set 100,000 ops 8ms 12,573,069/sec get 100,000 ops 4ms 24,821,181/sec reset 100,000 ops 4ms 25,324,412/sec scan 20 ops 8ms 2,430/sec delete 100,000 ops 5ms 22,156,034/sec memory 3,143,352 bytes 31/entry -- stdlib -- set 100,000 ops 7ms 13,547,121/sec get 100,000 ops 4ms 26,458,302/sec reset 100,000 ops 4ms 28,379,163/sec scan 20 ops 16ms 1,214/sec delete 100,000 ops 4ms 24,771,495/sec memory 2,784,264 bytes 27/entry ``` ## 1,000,000 random string keys ```shell ## STRING KEYS -- tidwall -- set 1,000,000 ops 217ms 4,607,387/sec get 1,000,000 ops 127ms 7,872,817/sec reset 1,000,000 ops 130ms 7,709,027/sec scan 20 ops 136ms 147/sec delete 1,000,000 ops 149ms 6,716,045/sec memory 67,108,848 bytes 67/entry -- stdlib -- set 1,000,000 ops 325ms 3,078,132/sec get 1,000,000 ops 122ms 8,217,771/sec reset 1,000,000 ops 133ms 7,510,273/sec scan 20 ops 163ms 122/sec delete 1,000,000 ops 148ms 6,761,332/sec memory 57,931,472 bytes 57/entry ``` ## 1,000,000 random int keys ```shell ## INT KEYS -- tidwall -- set 1,000,000 ops 101ms 9,901,395/sec get 1,000,000 ops 63ms 15,928,770/sec reset 1,000,000 ops 66ms 15,107,262/sec scan 20 ops 139ms 144/sec delete 1,000,000 ops 66ms 15,216,322/sec memory 50,329,272 bytes 50/entry -- stdlib -- set 1,000,000 ops 119ms 8,431,961/sec get 1,000,000 ops 61ms 16,376,595/sec reset 1,000,000 ops 59ms 17,032,395/sec scan 20 ops 153ms 130/sec delete 1,000,000 ops 67ms 15,026,654/sec memory 40,146,760 bytes 40/entry ``` ## 10,000,000 random string keys (int values) ```shell ## STRING KEYS -- tidwall -- set 10,000,000 ops 2584ms 3,869,389/sec get 10,000,000 ops 1418ms 7,051,328/sec reset 10,000,000 ops 1469ms 6,807,487/sec scan 20 ops 1049ms 19/sec delete 10,000,000 ops 1694ms 5,901,787/sec memory 536,870,896 bytes 53/entry -- stdlib -- set 10,000,000 ops 3771ms 2,651,828/sec get 10,000,000 ops 1494ms 6,695,021/sec reset 10,000,000 ops 1480ms 6,758,881/sec scan 20 ops 1855ms 10/sec delete 10,000,000 ops 1629ms 6,138,209/sec memory 463,468,240 bytes 46/entry ``` ## 10,000,000 random int keys (int values) ```shell ## INT KEYS -- tidwall -- set 10,000,000 ops 1428ms 7,002,173/sec get 10,000,000 ops 733ms 13,636,196/sec reset 10,000,000 ops 787ms 12,710,144/sec scan 20 ops 1098ms 18/sec delete 10,000,000 ops 900ms 11,108,541/sec memory 402,650,808 bytes 40/entry -- stdlib -- set 10,000,000 ops 1709ms 5,850,969/sec get 10,000,000 ops 797ms 12,551,221/sec reset 10,000,000 ops 874ms 11,437,820/sec scan 20 ops 1629ms 12/sec delete 10,000,000 ops 910ms 10,994,436/sec memory 321,976,032 bytes 32/entry ``` ================================================ FILE: LICENSE ================================================ Copyright 2019, Joshua J Baker Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ================================================ FILE: README.md ================================================ # hashmap [![GoDoc](https://img.shields.io/badge/api-reference-blue.svg?style=flat-square)](https://godoc.org/github.com/tidwall/hashmap) An [efficient](BENCH.md) hashmap implementation in Go. ## Features - Support for [Generics](#generics). - `Map` and `Set` types for unordered key-value maps and sets, - [xxh3 algorithm](https://github.com/zeebo/xxh3) - [Open addressing](https://en.wikipedia.org/wiki/Hash_table#Open_addressing) with [Robin hood hashing](https://en.wikipedia.org/wiki/Hash_table#Robin_Hood_hashing) - Automatically shinks memory on deletes (no memory leaks). - Pretty darn good performance. 🚀 ([benchmarks](BENCH.md)). For ordered key-value data, check out the [tidwall/btree](https://github.com/tidwall/btree) package. ## Getting Started ### Installing To start using `hashmap`, install Go and run `go get`: ```sh go get github.com/tidwall/hashmap ``` This will retrieve the library. ## Usage The `Map` type works similar to a standard Go map, and includes the methods: `Set`, `Get`, `Delete`, `Len`, `Scan`, `Keys`, `Values`, and `Copy`. ```go var m hashmap.Map[string, string] m.Set("Hello", "Dolly!") val, _ := m.Get("Hello") fmt.Printf("%v\n", val) val, _ = m.Delete("Hello") fmt.Printf("%v\n", val) val, _ = m.Get("Hello") fmt.Printf("%v\n", val) // Output: // Dolly! // Dolly! // ``` The `Set` type is like `Map` but only for keys. It includes the methods: `Insert`, `Contains`, `Delete`, `Len`, `Scan` and `Keys`. ```go var m hashmap.Set[string] m.Insert("Andy") m.Insert("Kate") m.Insert("Janet") fmt.Printf("%v\n", m.Contains("Kate")) fmt.Printf("%v\n", m.Contains("Bob")) fmt.Printf("%v\n", m.Contains("Andy")) // Output: // true // false // true ``` ## Performance See [BENCH.md](BENCH.md) for more info. ## Contact Josh Baker [@tidwall](http://twitter.com/tidwall) ## License Source code is available under the MIT [License](LICENSE). ================================================ FILE: go.mod ================================================ module github.com/tidwall/hashmap go 1.18 require github.com/zeebo/xxh3 v1.0.2 require github.com/klauspost/cpuid/v2 v2.0.9 // indirect ================================================ FILE: go.sum ================================================ github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= ================================================ FILE: map.go ================================================ // Copyright 2019 Joshua J Baker. All rights reserved. // Use of this source code is governed by an ISC-style // license that can be found in the LICENSE file. package hashmap import ( "unsafe" "github.com/zeebo/xxh3" ) const ( loadFactor = 0.85 // must be above 50% dibBitSize = 16 // 0xFFFF hashBitSize = 64 - dibBitSize // 0xFFFFFFFFFFFF maxHash = ^uint64(0) >> dibBitSize // max 28,147,497,671,0655 maxDIB = ^uint64(0) >> hashBitSize // max 65,535 ) type entry[K comparable, V any] struct { hdib uint64 // bitfield { hash:48 dib:16 } value V // user value key K // user key } func (e *entry[K, V]) dib() int { return int(e.hdib & maxDIB) } func (e *entry[K, V]) hash() int { return int(e.hdib >> dibBitSize) } func (e *entry[K, V]) setDIB(dib int) { e.hdib = e.hdib>>dibBitSize<> dibBitSize) } // Map is a hashmap. Like map[string]interface{} type Map[K comparable, V any] struct { cap int length int mask int growAt int shrinkAt int buckets []entry[K, V] ksize int kstr bool } // New returns a new Map. Like map[string]interface{} func New[K comparable, V any](cap int) *Map[K, V] { m := new(Map[K, V]) m.cap = cap sz := 8 for sz < m.cap { sz *= 2 } if m.cap > 0 { m.cap = sz } m.buckets = make([]entry[K, V], sz) m.mask = len(m.buckets) - 1 m.growAt = int(float64(len(m.buckets)) * loadFactor) m.shrinkAt = int(float64(len(m.buckets)) * (1 - loadFactor)) m.detectHasher() return m } func (m *Map[K, V]) detectHasher() { // Detect the key type. This is needed by the hasher. var k K switch ((interface{})(k)).(type) { case string: m.kstr = true default: m.ksize = int(unsafe.Sizeof(k)) } } func (m *Map[K, V]) resize(newCap int) { nmap := New[K, V](newCap) for i := 0; i < len(m.buckets); i++ { if m.buckets[i].dib() > 0 { nmap.set(m.buckets[i].hash(), m.buckets[i].key, m.buckets[i].value) } } cap := m.cap *m = *nmap m.cap = cap } // Set assigns a value to a key. // Returns the previous value, or false when no value was assigned. func (m *Map[K, V]) Set(key K, value V) (V, bool) { if len(m.buckets) == 0 { *m = *New[K, V](0) } if m.length >= m.growAt { m.resize(len(m.buckets) * 2) } return m.set(m.hash(key), key, value) } func (m *Map[K, V]) set(hash int, key K, value V) (prev V, ok bool) { e := entry[K, V]{makeHDIB(hash, 1), value, key} i := e.hash() & m.mask for { if m.buckets[i].dib() == 0 { m.buckets[i] = e m.length++ return prev, false } if e.hash() == m.buckets[i].hash() && e.key == m.buckets[i].key { prev = m.buckets[i].value m.buckets[i].value = e.value return prev, true } if m.buckets[i].dib() < e.dib() { e, m.buckets[i] = m.buckets[i], e } i = (i + 1) & m.mask e.setDIB(e.dib() + 1) } } // Get returns a value for a key. // Returns false when no value has been assign for key. func (m *Map[K, V]) Get(key K) (value V, ok bool) { if len(m.buckets) == 0 { return value, false } hash := m.hash(key) i := hash & m.mask for { if m.buckets[i].dib() == 0 { return value, false } if m.buckets[i].hash() == hash && m.buckets[i].key == key { return m.buckets[i].value, true } i = (i + 1) & m.mask } } // Len returns the number of values in map. func (m *Map[K, V]) Len() int { return m.length } // Delete deletes a value for a key. // Returns the deleted value, or false when no value was assigned. func (m *Map[K, V]) Delete(key K) (prev V, deleted bool) { if len(m.buckets) == 0 { return prev, false } hash := m.hash(key) i := hash & m.mask for { if m.buckets[i].dib() == 0 { return prev, false } if m.buckets[i].hash() == hash && m.buckets[i].key == key { prev = m.buckets[i].value m.remove(i) return prev, true } i = (i + 1) & m.mask } } func (m *Map[K, V]) remove(i int) { m.buckets[i].setDIB(0) for { pi := i i = (i + 1) & m.mask if m.buckets[i].dib() <= 1 { m.buckets[pi] = entry[K, V]{} break } m.buckets[pi] = m.buckets[i] m.buckets[pi].setDIB(m.buckets[pi].dib() - 1) } m.length-- if len(m.buckets) > m.cap && m.length <= m.shrinkAt { m.resize(m.length) } } // Scan iterates over all key/values. // It's not safe to call or Set or Delete while scanning. func (m *Map[K, V]) Scan(iter func(key K, value V) bool) { for i := 0; i < len(m.buckets); i++ { if m.buckets[i].dib() > 0 { if !iter(m.buckets[i].key, m.buckets[i].value) { return } } } } // Keys returns all keys as a slice func (m *Map[K, V]) Keys() []K { keys := make([]K, 0, m.length) for i := 0; i < len(m.buckets); i++ { if m.buckets[i].dib() > 0 { keys = append(keys, m.buckets[i].key) } } return keys } // Values returns all values as a slice func (m *Map[K, V]) Values() []V { values := make([]V, 0, m.length) for i := 0; i < len(m.buckets); i++ { if m.buckets[i].dib() > 0 { values = append(values, m.buckets[i].value) } } return values } // Copy the hashmap. func (m *Map[K, V]) Copy() *Map[K, V] { m2 := new(Map[K, V]) *m2 = *m m2.buckets = make([]entry[K, V], len(m.buckets)) copy(m2.buckets, m.buckets) return m2 } // GetPos gets a single keys/value nearby a position. // The pos param can be any valid uint64. Useful for grabbing a random item // from the map. func (m *Map[K, V]) GetPos(pos uint64) (key K, value V, ok bool) { for i := 0; i < len(m.buckets); i++ { index := (pos + uint64(i)) & uint64(m.mask) if m.buckets[index].dib() > 0 { return m.buckets[index].key, m.buckets[index].value, true } } // Empty map return key, value, false } ================================================ FILE: map_test.go ================================================ // Copyright 2019 Joshua J Baker. All rights reserved. // Use of this source code is governed by an ISC-style // license that can be found in the LICENSE file. package hashmap import ( "fmt" "math/rand" "os" "reflect" "runtime" "sort" "strconv" "sync" "testing" "time" ) type keyT = string type valueT = interface{} func k(key int) keyT { return strconv.FormatInt(int64(key), 10) } func add(x keyT, delta int) int { i, err := strconv.ParseInt(x, 10, 64) if err != nil { panic(err) } return int(i + int64(delta)) } // ///////////////////////// func random(N int, perm bool) []keyT { nums := make([]keyT, N) if perm { for i, x := range rand.Perm(N) { nums[i] = k(x) } } else { m := make(map[keyT]bool) for len(m) < N { m[k(int(rand.Uint64()))] = true } var i int for k := range m { nums[i] = k i++ } } return nums } func shuffle[K comparable](nums []K) { for i := range nums { j := rand.Intn(i + 1) nums[i], nums[j] = nums[j], nums[i] } } func init() { //var seed int64 = 1519776033517775607 seed := (time.Now().UnixNano()) println("seed:", seed) rand.Seed(seed) } type imap struct { m *Map[string, interface{}] } func newimap(cap int) *imap { m := new(imap) m.m = New[string, interface{}](cap) return m } func (m *imap) Get(key string) (interface{}, bool) { if m.m == nil { return nil, false } return m.m.Get(key) } func (m *imap) Set(key string, value interface{}) (interface{}, bool) { if m.m == nil { m.m = new(Map[string, interface{}]) } return m.m.Set(key, value) } func (m *imap) Delete(key string) (interface{}, bool) { if m.m == nil { return nil, false } return m.m.Delete(key) } func (m *imap) Len() int { if m.m == nil { return 0 } return m.m.Len() } func (m *imap) Scan(iter func(key string, value interface{}) bool) { if m.m == nil { return } m.m.Scan(iter) } func TestRandomData(t *testing.T) { N := 10000 start := time.Now() for time.Since(start) < time.Second*2 { nums := random(N, true) var m *imap switch rand.Int() % 5 { default: m = newimap(N / ((rand.Int() % 3) + 1)) case 1: m = new(imap) case 2: m = newimap(0) } v, ok := m.Get(k(999)) if ok || v != nil { t.Fatalf("expected %v, got %v", nil, v) } v, ok = m.Delete(k(999)) if ok || v != nil { t.Fatalf("expected %v, got %v", nil, v) } if m.Len() != 0 { t.Fatalf("expected %v, got %v", 0, m.Len()) } // set a bunch of items for i := 0; i < len(nums); i++ { v, ok := m.Set(nums[i], nums[i]) if ok || v != nil { t.Fatalf("expected %v, got %v", nil, v) } } if m.Len() != N { t.Fatalf("expected %v, got %v", N, m.Len()) } // retrieve all the items shuffle(nums) for i := 0; i < len(nums); i++ { v, ok := m.Get(nums[i]) if !ok || v == nil || v != nums[i] { t.Fatalf("expected %v, got %v", nums[i], v) } } // replace all the items shuffle(nums) for i := 0; i < len(nums); i++ { v, ok := m.Set(nums[i], add(nums[i], 1)) if !ok || v != nums[i] { t.Fatalf("expected %v, got %v", nums[i], v) } } if m.Len() != N { t.Fatalf("expected %v, got %v", N, m.Len()) } // retrieve all the items shuffle(nums) for i := 0; i < len(nums); i++ { v, ok := m.Get(nums[i]) if !ok || v != add(nums[i], 1) { t.Fatalf("expected %v, got %v", add(nums[i], 1), v) } } // remove half the items shuffle(nums) for i := 0; i < len(nums)/2; i++ { v, ok := m.Delete(nums[i]) if !ok || v != add(nums[i], 1) { t.Fatalf("expected %v, got %v", add(nums[i], 1), v) } } if m.Len() != N/2 { t.Fatalf("expected %v, got %v", N/2, m.Len()) } // check to make sure that the items have been removed for i := 0; i < len(nums)/2; i++ { v, ok := m.Get(nums[i]) if ok || v != nil { t.Fatalf("expected %v, got %v", nil, v) } } // check the second half of the items for i := len(nums) / 2; i < len(nums); i++ { v, ok := m.Get(nums[i]) if !ok || v != add(nums[i], 1) { t.Fatalf("expected %v, got %v", add(nums[i], 1), v) } } // try to delete again, make sure they don't exist for i := 0; i < len(nums)/2; i++ { v, ok := m.Delete(nums[i]) if ok || v != nil { t.Fatalf("expected %v, got %v", nil, v) } } if m.Len() != N/2 { t.Fatalf("expected %v, got %v", N/2, m.Len()) } m.Scan(func(key keyT, value valueT) bool { if value != add(key, 1) { t.Fatalf("expected %v, got %v", add(key, 1), value) } return true }) var n int m.Scan(func(key keyT, value valueT) bool { n++ return false }) if n != 1 { t.Fatalf("expected %v, got %v", 1, n) } for i := len(nums) / 2; i < len(nums); i++ { v, ok := m.Delete(nums[i]) if !ok || v != add(nums[i], 1) { t.Fatalf("expected %v, got %v", add(nums[i], 1), v) } } } } func TestBench(t *testing.T) { N, _ := strconv.ParseUint(os.Getenv("MAPBENCH"), 10, 64) if N == 0 { fmt.Printf("Enable benchmarks with MAPBENCH=1000000\n") return } var pnums []int for i := 0; i < int(N); i++ { pnums = append(pnums, i) } { fmt.Printf("\n## STRING KEYS\n\n") nums := random(int(N), false) t.Run("Tidwall", func(t *testing.T) { testPerf(nums, pnums, "tidwall") }) t.Run("Stdlib", func(t *testing.T) { testPerf(nums, pnums, "stdlib") }) } { fmt.Printf("\n## INT KEYS\n\n") nums := rand.Perm(int(N)) t.Run("Tidwall", func(t *testing.T) { testPerf(nums, pnums, "tidwall") }) t.Run("Stdlib", func(t *testing.T) { testPerf(nums, pnums, "stdlib") }) } } func printItem(s string, size int, dir int) { for len(s) < size { if dir == -1 { s += " " } else { s = " " + s } } fmt.Printf("%s ", s) } func testPerf[K comparable, V any](nums []K, pnums []V, which string) { var ms1, ms2 runtime.MemStats initSize := 0 //len(nums) * 2 defer func() { heapBytes := int(ms2.HeapAlloc - ms1.HeapAlloc) fmt.Printf("memory %13s bytes %19s/entry \n", commaize(heapBytes), commaize(heapBytes/len(nums))) fmt.Printf("\n") }() runtime.GC() time.Sleep(time.Millisecond * 100) runtime.ReadMemStats(&ms1) var setop, getop, delop func(int, int) var scnop func() switch which { case "stdlib": m := make(map[K]V, initSize) setop = func(i, _ int) { m[nums[i]] = pnums[i] } getop = func(i, _ int) { _ = m[nums[i]] } delop = func(i, _ int) { delete(m, nums[i]) } scnop = func() { for range m { } } case "tidwall": var m Map[K, V] setop = func(i, _ int) { m.Set(nums[i], pnums[i]) } getop = func(i, _ int) { m.Get(nums[i]) } delop = func(i, _ int) { m.Delete(nums[i]) } scnop = func() { m.Scan(func(key K, value V) bool { return true }) } } fmt.Printf("-- %s --", which) fmt.Printf("\n") ops := []func(int, int){setop, getop, setop, nil, delop} tags := []string{"set", "get", "reset", "scan", "delete"} for i := range ops { shuffle(nums) var na bool var n int start := time.Now() if tags[i] == "scan" { op := scnop if op == nil { na = true } else { n = 20 for i := 0; i < n; i++ { op() } } } else { n = len(nums) for j := 0; j < n; j++ { ops[i](j, 1) } } dur := time.Since(start) if i == 0 { runtime.GC() time.Sleep(time.Millisecond * 100) runtime.ReadMemStats(&ms2) } printItem(tags[i], 9, -1) if na { printItem("-- unavailable --", 14, 1) } else { if n == -1 { printItem("unknown ops", 14, 1) } else { printItem(fmt.Sprintf("%s ops", commaize(n)), 14, 1) } printItem(fmt.Sprintf("%.0fms", dur.Seconds()*1000), 8, 1) if n != -1 { printItem(fmt.Sprintf("%s/sec", commaize(int(float64(n)/dur.Seconds()))), 18, 1) } } fmt.Printf("\n") } } func commaize(n int) string { s1, s2 := fmt.Sprintf("%d", n), "" for i, j := len(s1)-1, 0; i >= 0; i, j = i-1, j+1 { if j%3 == 0 && j != 0 { s2 = "," + s2 } s2 = string(s1[i]) + s2 } return s2 } func TestHashDIB(t *testing.T) { var e entry[string, interface{}] e.setDIB(100) e.setHash(90000) if e.dib() != 100 { t.Fatalf("expected %v, got %v", 100, e.dib()) } if e.hash() != 90000 { t.Fatalf("expected %v, got %v", 90000, e.hash()) } } func TestIntInt(t *testing.T) { var m Map[int, int] keys := rand.Perm(1000000) for i := 0; i < len(keys); i++ { _, ok := m.Set(keys[i], keys[i]*10) if ok { t.Fatalf("expected false") } if m.Len() != i+1 { t.Fatalf("expected %d got %d", i+1, m.Len()) } } for i := 0; i < len(keys); i++ { v, ok := m.Get(keys[i]) if !ok { t.Fatalf("expected true") } if v != keys[i]*10 { t.Fatalf("expected %d got %d", keys[i]*10, v) } } for i := 0; i < len(keys); i++ { v, ok := m.Delete(keys[i]) if !ok { t.Fatalf("expected true") } if v != keys[i]*10 { t.Fatalf("expected %d got %d", keys[i]*10, v) } if m.Len() != len(keys)-i-1 { t.Fatalf("expected %d got %d", len(keys)-i-1, m.Len()) } } } func TestMapValues(t *testing.T) { var m Map[int, int] m.Set(1, 2) expect := []int{2} got := m.Values() if !reflect.DeepEqual(got, expect) { t.Fatal("expected Values equal") } } func copyMapEntries(m *Map[int, int]) []entry[int, int] { all := make([]entry[int, int], m.Len()) keys := m.Keys() vals := m.Values() for i := 0; i < len(keys); i++ { all[i].key = keys[i] all[i].value = vals[i] } sort.Slice(all, func(i, j int) bool { return all[i].key < all[j].key }) return all } func mapEntriesEqual(a, b []entry[int, int]) bool { return reflect.DeepEqual(a, b) } func copyMapTest(N int, m1 *Map[int, int], e11 []entry[int, int], deep bool) { e12 := copyMapEntries(m1) if !mapEntriesEqual(e11, e12) { panic("!") } // Make a copy and compare the values m2 := m1.Copy() e21 := copyMapEntries(m1) if !mapEntriesEqual(e21, e12) { panic("!") } // Delete every other key var e22 []entry[int, int] for i, j := range rand.Perm(N) { if i&1 == 0 { e22 = append(e22, e21[j]) } else { prev, deleted := m2.Delete(e21[j].key) if !deleted { panic("!") } if prev != e21[j].value { panic("!") } } } if m2.Len() != N/2 { panic("!") } sort.Slice(e22, func(i, j int) bool { return e22[i].key < e22[j].key }) e23 := copyMapEntries(m2) if !mapEntriesEqual(e23, e22) { panic("!") } if !deep { var wg sync.WaitGroup wg.Add(2) go func() { defer wg.Done() copyMapTest(N/2, m2, e23, true) }() go func() { defer wg.Done() copyMapTest(N/2, m2, e23, true) }() wg.Wait() } e24 := copyMapEntries(m2) if !mapEntriesEqual(e24, e23) { panic("!") } } func TestMapCopy(t *testing.T) { N := 1_000 // create the initial map m1 := New[int, int](0) for m1.Len() < N { m1.Set(rand.Int(), rand.Int()) } e11 := copyMapEntries(m1) dur := time.Second * 2 var wg sync.WaitGroup for i := 0; i < 16; i++ { wg.Add(1) go func() { defer wg.Done() start := time.Now() for time.Since(start) < dur { copyMapTest(N, m1, e11, false) } }() } wg.Wait() e12 := copyMapEntries(m1) if !mapEntriesEqual(e11, e12) { panic("!") } } func TestEmpty(t *testing.T) { var m Map[int, int] if _, ok := m.Get(0); ok { t.Fatal() } if _, ok := m.Delete(0); ok { t.Fatal() } } func TestGetPos(t *testing.T) { var m Map[int, int] if _, _, ok := m.GetPos(100); ok { t.Fatal() } for i := 0; i < 1000; i++ { m.Set(i, i+1) } m2 := make(map[int]int) for i := 0; i < 10000; i++ { key, val, ok := m.GetPos(uint64(i)) if !ok { t.Fatal() } m2[key] = val } if len(m2) != m.Len() { t.Fatal() } } func TestIssue3(t *testing.T) { m := New[string, int](50) m.Set("key:808943", 1) m.Set("key:5834", 2) m.Set("key:51630", 3) m.Set("key:49504", 4) m.Set("key:346528", 5) m.Set("key:189743", 6) m.Set("key:4112608", 7) m.Set("key:21749", 8) m.Set("key:844131", 9) if v, _ := m.Delete("key:844131"); v != 9 { t.Fatal() } if _, ok := m.Get("key:844131"); ok { t.Fatal() } for j := 0; j < 1000; j++ { m = New[string, int](50) keys := make([]string, j) for i := 0; i < len(keys); i++ { keys[i] = fmt.Sprintf("key:%d", i) m.Set(keys[i], i) } for i := 0; i < len(keys); i++ { if v, _ := m.Get(keys[i]); v != i { t.Fatal() } if v, _ := m.Delete(keys[i]); v != i { t.Fatal() } if _, ok := m.Get(keys[i]); ok { t.Fatal() } } } } ================================================ FILE: set.go ================================================ package hashmap type Set[K comparable] struct { base Map[K, struct{}] } // Insert an item func (tr *Set[K]) Insert(key K) { tr.base.Set(key, struct{}{}) } // Get a value for key func (tr *Set[K]) Contains(key K) bool { _, ok := tr.base.Get(key) return ok } // Len returns the number of items in the tree func (tr *Set[K]) Len() int { return tr.base.Len() } // Delete an item func (tr *Set[K]) Delete(key K) { tr.base.Delete(key) } func (tr *Set[K]) Scan(iter func(key K) bool) { tr.base.Scan(func(key K, value struct{}) bool { return iter(key) }) } // Keys returns all keys as a slice func (tr *Set[K]) Keys() []K { return tr.base.Keys() } // Copy the set. This is a copy-on-write operation and is very fast because // it only performs a shadow copy. func (tr *Set[K]) Copy() *Set[K] { tr2 := new(Set[K]) tr2.base = *tr.base.Copy() return tr2 } // GetPos gets a single keys/value nearby a position. // The pos param can be any valid uint64. Useful for grabbing a random item // from the Set. func (s *Set[K]) GetPos(pos uint64) (key K, ok bool) { key, _, ok = s.base.GetPos(pos) return key, ok } ================================================ FILE: set_test.go ================================================ package hashmap import ( "math/rand" "reflect" "sort" "sync" "testing" "time" ) func TestSet(t *testing.T) { var s Set[int] keys := rand.Perm(1000000) for i := 0; i < len(keys); i++ { s.Insert(keys[i]) if s.Len() != i+1 { t.Fatalf("expected %d got %d", i+1, s.Len()) } } for i := 0; i < len(keys); i++ { ok := s.Contains(keys[i]) if !ok { t.Fatalf("expected true") } } var skeys []int s.Scan(func(key int) bool { skeys = append(skeys, key) return true }) if len(skeys) != s.Len() { t.Fatalf("expected %d got %d", len(skeys), s.Len()) } for i := 0; i < len(keys); i++ { s.Delete(keys[i]) if s.Len() != len(keys)-i-1 { t.Fatalf("expected %d got %d", len(keys)-i-1, s.Len()) } } } func TestSetKeys(t *testing.T) { var s Set[string] s.Insert("key") expect := []string{"key"} got := s.Keys() if !reflect.DeepEqual(got, expect) { t.Fatal("expected Keys equal") } } func copySetEntries(m *Set[int]) []int { all := m.Keys() sort.Ints(all) return all } func setEntriesEqual(a, b []int) bool { return reflect.DeepEqual(a, b) } func copySetTest(N int, s1 *Set[int], e11 []int, deep bool) { e12 := copySetEntries(s1) if !setEntriesEqual(e11, e12) { panic("!") } // Make a copy and compare the values s2 := s1.Copy() e21 := copySetEntries(s1) if !setEntriesEqual(e21, e12) { panic("!") } // Delete every other key var e22 []int for i, j := range rand.Perm(N) { if i&1 == 0 { e22 = append(e22, e21[j]) } else { s2.Delete(e21[j]) } } if s2.Len() != N/2 { panic("!") } sort.Ints(e22) e23 := copySetEntries(s2) if !setEntriesEqual(e23, e22) { panic("!") } if !deep { var wg sync.WaitGroup wg.Add(2) go func() { defer wg.Done() copySetTest(N/2, s2, e23, true) }() go func() { defer wg.Done() copySetTest(N/2, s2, e23, true) }() wg.Wait() } e24 := copySetEntries(s2) if !setEntriesEqual(e24, e23) { panic("!") } } func TestSetCopy(t *testing.T) { N := 1_000 // create the initial map s1 := new(Set[int]) for s1.Len() < N { s1.Insert(rand.Int()) } e11 := copySetEntries(s1) dur := time.Second * 2 var wg sync.WaitGroup for i := 0; i < 16; i++ { wg.Add(1) go func() { defer wg.Done() start := time.Now() for time.Since(start) < dur { copySetTest(N, s1, e11, false) } }() } wg.Wait() e12 := copySetEntries(s1) if !setEntriesEqual(e11, e12) { panic("!") } } func TestSetGetPos(t *testing.T) { var m Set[int] if _, ok := m.GetPos(100); ok { t.Fatal() } for i := 0; i < 1000; i++ { m.Insert(i) } m2 := make(map[int]int) for i := 0; i < 10000; i++ { key, ok := m.GetPos(uint64(i)) if !ok { t.Fatal() } m2[key] = key } if len(m2) != m.Len() { t.Fatal() } }