diff options
author | Ben Johnson <benbjohnson@yahoo.com> | 2022-12-20 14:31:29 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-12-20 14:31:29 -0700 |
commit | 8932b999621d52733cab554482344864d128b9ba (patch) | |
tree | 6fbf538702a2180a8b9ddd980036d3dc948ea492 | |
parent | Merge pull request #28 from BarrensZeppelin/master (diff) | |
parent | generic-widening: improve docs (diff) | |
download | pds-8932b999621d52733cab554482344864d128b9ba.tar.gz pds-8932b999621d52733cab554482344864d128b9ba.tar.xz |
Merge pull request #29 from laher/map-keys-comparable
generics: widen map key constraint to 'comparable'
-rw-r--r-- | immutable.go | 128 |
1 files changed, 81 insertions, 47 deletions
diff --git a/immutable.go b/immutable.go index 68e9580..2b91c9c 100644 --- a/immutable.go +++ b/immutable.go @@ -1,6 +1,6 @@ // Package immutable provides immutable collection types. // -// Introduction +// # Introduction // // Immutable collections provide an efficient, safe way to share collections // of data while minimizing locks. The collections in this package provide @@ -14,7 +14,7 @@ // with Go's built-in collection types so please evaluate for your specific // use. // -// Collection Types +// # Collection Types // // The List type provides an API similar to Go slices. They allow appending, // prepending, and updating of elements. Elements can also be fetched by index @@ -28,7 +28,7 @@ // provides iteration over unsorted keys. Maps improved performance and memory // usage as compared to SortedMaps. // -// Hashing and Sorting +// # Hashing and Sorting // // Map types require the use of a Hasher implementation to calculate hashes for // their keys and check for key equality. SortedMaps require the use of a @@ -507,7 +507,7 @@ func (n *listLeafNode[T]) deleteBefore(index int, mutable bool) listNode[T] { copy(other.children[idx:][:], n.children[idx:][:]) } // Set the first idx bits to 0. - other.occupied &= ^((1 << idx)-1) + other.occupied &= ^((1 << idx) - 1) return other } @@ -530,7 +530,7 @@ func (n *listLeafNode[T]) deleteAfter(index int, mutable bool) listNode[T] { copy(other.children[:idx+1][:], n.children[:idx+1][:]) } // Set bits after idx to 0. idx < 31 because n.containsAfter(index) == true. - other.occupied &= (1 << (idx+1))-1 + other.occupied &= (1 << (idx + 1)) - 1 return other } @@ -680,7 +680,7 @@ const ( // to generate hashes and check for equality of key values. // // It is implemented as an Hash Array Mapped Trie. -type Map[K constraints.Ordered, V any] struct { +type Map[K comparable, V any] struct { size int // total number of key/value pairs root mapNode[K, V] // root node of trie hasher Hasher[K] // hasher implementation @@ -689,7 +689,7 @@ type Map[K constraints.Ordered, V any] struct { // NewMap returns a new instance of Map. If hasher is nil, a default hasher // implementation will automatically be chosen based on the first key added. // Default hasher implementations only exist for int, string, and byte slice types. -func NewMap[K constraints.Ordered, V any](hasher Hasher[K]) *Map[K, V] { +func NewMap[K comparable, V any](hasher Hasher[K]) *Map[K, V] { return &Map[K, V]{ hasher: hasher, } @@ -796,12 +796,12 @@ func (m *Map[K, V]) Iterator() *MapIterator[K, V] { } // MapBuilder represents an efficient builder for creating Maps. -type MapBuilder[K constraints.Ordered, V any] struct { +type MapBuilder[K comparable, V any] struct { m *Map[K, V] // current state } // NewMapBuilder returns a new instance of MapBuilder. -func NewMapBuilder[K constraints.Ordered, V any](hasher Hasher[K]) *MapBuilder[K, V] { +func NewMapBuilder[K comparable, V any](hasher Hasher[K]) *MapBuilder[K, V] { return &MapBuilder[K, V]{m: NewMap[K, V](hasher)} } @@ -845,7 +845,7 @@ func (b *MapBuilder[K, V]) Iterator() *MapIterator[K, V] { } // mapNode represents any node in the map tree. -type mapNode[K constraints.Ordered, V any] interface { +type mapNode[K comparable, V any] interface { get(key K, shift uint, keyHash uint32, h Hasher[K]) (value V, ok bool) set(key K, value V, shift uint, keyHash uint32, h Hasher[K], mutable bool, resized *bool) mapNode[K, V] delete(key K, shift uint, keyHash uint32, h Hasher[K], mutable bool, resized *bool) mapNode[K, V] @@ -858,7 +858,7 @@ var _ mapNode[string, any] = (*mapValueNode[string, any])(nil) var _ mapNode[string, any] = (*mapHashCollisionNode[string, any])(nil) // mapLeafNode represents a node that stores a single key hash at the leaf of the map tree. -type mapLeafNode[K constraints.Ordered, V any] interface { +type mapLeafNode[K comparable, V any] interface { mapNode[K, V] keyHashValue() uint32 } @@ -869,7 +869,7 @@ var _ mapLeafNode[string, any] = (*mapHashCollisionNode[string, any])(nil) // mapArrayNode is a map node that stores key/value pairs in a slice. // Entries are stored in insertion order. An array node expands into a bitmap // indexed node once a given threshold size is crossed. -type mapArrayNode[K constraints.Ordered, V any] struct { +type mapArrayNode[K comparable, V any] struct { entries []mapEntry[K, V] } @@ -971,7 +971,7 @@ func (n *mapArrayNode[K, V]) delete(key K, shift uint, keyHash uint32, h Hasher[ // mapBitmapIndexedNode represents a map branch node with a variable number of // node slots and indexed using a bitmap. Indexes for the node slots are // calculated by counting the number of set bits before the target bit using popcount. -type mapBitmapIndexedNode[K constraints.Ordered, V any] struct { +type mapBitmapIndexedNode[K comparable, V any] struct { bitmap uint32 nodes []mapNode[K, V] } @@ -1117,7 +1117,7 @@ func (n *mapBitmapIndexedNode[K, V]) delete(key K, shift uint, keyHash uint32, h // mapHashArrayNode is a map branch node that stores nodes in a fixed length // array. Child nodes are indexed by their index bit segment for the current depth. -type mapHashArrayNode[K constraints.Ordered, V any] struct { +type mapHashArrayNode[K comparable, V any] struct { count uint // number of set nodes nodes [mapNodeSize]mapNode[K, V] // child node slots, may contain empties } @@ -1213,14 +1213,14 @@ func (n *mapHashArrayNode[K, V]) delete(key K, shift uint, keyHash uint32, h Has // mapValueNode represents a leaf node with a single key/value pair. // A value node can be converted to a hash collision leaf node if a different // key with the same keyHash is inserted. -type mapValueNode[K constraints.Ordered, V any] struct { +type mapValueNode[K comparable, V any] struct { keyHash uint32 key K value V } // newMapValueNode returns a new instance of mapValueNode. -func newMapValueNode[K constraints.Ordered, V any](keyHash uint32, key K, value V) *mapValueNode[K, V] { +func newMapValueNode[K comparable, V any](keyHash uint32, key K, value V) *mapValueNode[K, V] { return &mapValueNode[K, V]{ keyHash: keyHash, key: key, @@ -1285,7 +1285,7 @@ func (n *mapValueNode[K, V]) delete(key K, shift uint, keyHash uint32, h Hasher[ // mapHashCollisionNode represents a leaf node that contains two or more key/value // pairs with the same key hash. Single pairs for a hash are stored as value nodes. -type mapHashCollisionNode[K constraints.Ordered, V any] struct { +type mapHashCollisionNode[K comparable, V any] struct { keyHash uint32 // key hash for all entries entries []mapEntry[K, V] } @@ -1391,7 +1391,7 @@ func (n *mapHashCollisionNode[K, V]) delete(key K, shift uint, keyHash uint32, h // mergeIntoNode merges a key/value pair into an existing node. // Caller must verify that node's keyHash is not equal to keyHash. -func mergeIntoNode[K constraints.Ordered, V any](node mapLeafNode[K, V], shift uint, keyHash uint32, key K, value V) mapNode[K, V] { +func mergeIntoNode[K comparable, V any](node mapLeafNode[K, V], shift uint, keyHash uint32, key K, value V) mapNode[K, V] { idx1 := (node.keyHashValue() >> shift) & mapNodeMask idx2 := (keyHash >> shift) & mapNodeMask @@ -1410,14 +1410,14 @@ func mergeIntoNode[K constraints.Ordered, V any](node mapLeafNode[K, V], shift u } // mapEntry represents a single key/value pair. -type mapEntry[K constraints.Ordered, V any] struct { +type mapEntry[K comparable, V any] struct { key K value V } // MapIterator represents an iterator over a map's key/value pairs. Although // map keys are not sorted, the iterator's order is deterministic. -type MapIterator[K constraints.Ordered, V any] struct { +type MapIterator[K comparable, V any] struct { m *Map[K, V] // source map stack [32]mapIteratorElem[K, V] // search stack @@ -1541,7 +1541,7 @@ func (itr *MapIterator[K, V]) first() { } // mapIteratorElem represents a node/index pair in the MapIterator stack. -type mapIteratorElem[K constraints.Ordered, V any] struct { +type mapIteratorElem[K comparable, V any] struct { node mapNode[K, V] index int } @@ -1555,7 +1555,7 @@ const ( // is determined by the Comparer used by the map. // // This map is implemented as a B+tree. -type SortedMap[K constraints.Ordered, V any] struct { +type SortedMap[K comparable, V any] struct { size int // total number of key/value pairs root sortedMapNode[K, V] // root of b+tree comparer Comparer[K] @@ -1564,7 +1564,7 @@ type SortedMap[K constraints.Ordered, V any] struct { // NewSortedMap returns a new instance of SortedMap. If comparer is nil then // a default comparer is set after the first key is inserted. Default comparers // exist for int, string, and byte slice keys. -func NewSortedMap[K constraints.Ordered, V any](comparer Comparer[K]) *SortedMap[K, V] { +func NewSortedMap[K comparable, V any](comparer Comparer[K]) *SortedMap[K, V] { return &SortedMap[K, V]{ comparer: comparer, } @@ -1594,7 +1594,7 @@ func (m *SortedMap[K, V]) set(key K, value V, mutable bool) *SortedMap[K, V] { // Set a comparer on the first value if one does not already exist. comparer := m.comparer if comparer == nil { - comparer = NewComparer[K](key) + comparer = NewComparer(key) } // Create copy, if necessary. @@ -1673,12 +1673,12 @@ func (m *SortedMap[K, V]) Iterator() *SortedMapIterator[K, V] { } // SortedMapBuilder represents an efficient builder for creating sorted maps. -type SortedMapBuilder[K constraints.Ordered, V any] struct { +type SortedMapBuilder[K comparable, V any] struct { m *SortedMap[K, V] // current state } // NewSortedMapBuilder returns a new instance of SortedMapBuilder. -func NewSortedMapBuilder[K constraints.Ordered, V any](comparer Comparer[K]) *SortedMapBuilder[K, V] { +func NewSortedMapBuilder[K comparable, V any](comparer Comparer[K]) *SortedMapBuilder[K, V] { return &SortedMapBuilder[K, V]{m: NewSortedMap[K, V](comparer)} } @@ -1722,7 +1722,7 @@ func (b *SortedMapBuilder[K, V]) Iterator() *SortedMapIterator[K, V] { } // sortedMapNode represents a branch or leaf node in the sorted map. -type sortedMapNode[K constraints.Ordered, V any] interface { +type sortedMapNode[K comparable, V any] interface { minKey() K indexOf(key K, c Comparer[K]) int get(key K, c Comparer[K]) (value V, ok bool) @@ -1734,12 +1734,12 @@ var _ sortedMapNode[string, any] = (*sortedMapBranchNode[string, any])(nil) var _ sortedMapNode[string, any] = (*sortedMapLeafNode[string, any])(nil) // sortedMapBranchNode represents a branch in the sorted map. -type sortedMapBranchNode[K constraints.Ordered, V any] struct { +type sortedMapBranchNode[K comparable, V any] struct { elems []sortedMapBranchElem[K, V] } // newSortedMapBranchNode returns a new branch node with the given child nodes. -func newSortedMapBranchNode[K constraints.Ordered, V any](children ...sortedMapNode[K, V]) *sortedMapBranchNode[K, V] { +func newSortedMapBranchNode[K comparable, V any](children ...sortedMapNode[K, V]) *sortedMapBranchNode[K, V] { // Fetch min keys for every child. elems := make([]sortedMapBranchElem[K, V], len(children)) for i, child := range children { @@ -1882,13 +1882,13 @@ func (n *sortedMapBranchNode[K, V]) delete(key K, c Comparer[K], mutable bool, r return other } -type sortedMapBranchElem[K constraints.Ordered, V any] struct { +type sortedMapBranchElem[K comparable, V any] struct { key K node sortedMapNode[K, V] } // sortedMapLeafNode represents a leaf node in the sorted map. -type sortedMapLeafNode[K constraints.Ordered, V any] struct { +type sortedMapLeafNode[K comparable, V any] struct { entries []mapEntry[K, V] } @@ -2003,7 +2003,7 @@ func (n *sortedMapLeafNode[K, V]) delete(key K, c Comparer[K], mutable bool, res // SortedMapIterator represents an iterator over a sorted map. // Iteration can occur in natural or reverse order based on use of Next() or Prev(). -type SortedMapIterator[K constraints.Ordered, V any] struct { +type SortedMapIterator[K comparable, V any] struct { m *SortedMap[K, V] // source map stack [32]sortedMapIteratorElem[K, V] // search stack @@ -2191,13 +2191,13 @@ func (itr *SortedMapIterator[K, V]) seek(key K) { } // sortedMapIteratorElem represents node/index pair in the SortedMapIterator stack. -type sortedMapIteratorElem[K constraints.Ordered, V any] struct { +type sortedMapIteratorElem[K comparable, V any] struct { node sortedMapNode[K, V] index int } // Hasher hashes keys and checks them for equality. -type Hasher[K constraints.Ordered] interface { +type Hasher[K comparable] interface { // Computes a hash for key. Hash(key K) uint32 @@ -2206,7 +2206,7 @@ type Hasher[K constraints.Ordered] interface { } // NewHasher returns the built-in hasher for a given key type. -func NewHasher[K constraints.Ordered](key K) Hasher[K] { +func NewHasher[K comparable](key K) Hasher[K] { // Attempt to use non-reflection based hasher first. switch (any(key)).(type) { case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, uintptr, string: @@ -2234,8 +2234,8 @@ func hashString(value string) uint32 { return hash } -// reflectIntHasher implements a reflection-based Hasher for int keys. -type reflectHasher[K constraints.Ordered] struct{} +// reflectIntHasher implements a reflection-based Hasher for keys. +type reflectHasher[K comparable] struct{} // Hash returns a hash for key. func (h *reflectHasher[K]) Hash(key K) uint32 { @@ -2256,7 +2256,7 @@ func (h *reflectHasher[K]) Hash(key K) uint32 { } // Equal returns true if a is equal to b. Otherwise returns false. -// Panics if a and b are not ints. +// Panics if a and b are not int-ish or string-ish. func (h *reflectHasher[K]) Equal(a, b K) bool { switch reflect.TypeOf(a).Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: @@ -2281,7 +2281,7 @@ func hashUint64(value uint64) uint32 { } // defaultHasher implements Hasher. -type defaultHasher[K constraints.Ordered] struct{} +type defaultHasher[K comparable] struct{} // Hash returns a hash for key. func (h *defaultHasher[K]) Hash(key K) uint32 { @@ -2322,14 +2322,16 @@ func (h *defaultHasher[K]) Equal(a, b K) bool { } // Comparer allows the comparison of two keys for the purpose of sorting. -type Comparer[K constraints.Ordered] interface { +type Comparer[K comparable] interface { // Returns -1 if a is less than b, returns 1 if a is greater than b, // and returns 0 if a is equal to b. Compare(a, b K) int } // NewComparer returns the built-in comparer for a given key type. -func NewComparer[K constraints.Ordered](key K) Comparer[K] { +// Note that only int-ish and string-ish types are supported, despite the 'comparable' constraint. +// Attempts to use other types will result in a panic - users should define their own Comparers for these cases. +func NewComparer[K comparable](key K) Comparer[K] { // Attempt to use non-reflection based comparer first. switch (any(key)).(type) { case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, uintptr, string: @@ -2346,12 +2348,44 @@ func NewComparer[K constraints.Ordered](key K) Comparer[K] { panic(fmt.Sprintf("immutable.NewComparer: must set comparer for %T type", key)) } -// defaultComparer compares two integers. Implements Comparer. -type defaultComparer[K constraints.Ordered] struct{} +// defaultComparer compares two values (int-ish and string-ish types are supported. Implements Comparer. +type defaultComparer[K comparable] struct{} // Compare returns -1 if a is less than b, returns 1 if a is greater than b, and -// returns 0 if a is equal to b. Panic if a or b is not an int. +// returns 0 if a is equal to b. Panic if a or b is not a string or int* type func (c *defaultComparer[K]) Compare(i K, j K) int { + switch x := (any(i)).(type) { + case int: + return defaultCompare(x, (any(j)).(int)) + case int8: + return defaultCompare(x, (any(j)).(int8)) + case int16: + return defaultCompare(x, (any(j)).(int16)) + case int32: + return defaultCompare(x, (any(j)).(int32)) + case int64: + return defaultCompare(x, (any(j)).(int64)) + case uint: + return defaultCompare(x, (any(j)).(uint)) + case uint8: + return defaultCompare(x, (any(j)).(uint8)) + case uint16: + return defaultCompare(x, (any(j)).(uint16)) + case uint32: + return defaultCompare(x, (any(j)).(uint32)) + case uint64: + return defaultCompare(x, (any(j)).(uint64)) + case uintptr: + return defaultCompare(x, (any(j)).(uintptr)) + case string: + return defaultCompare(x, (any(j)).(string)) + } + panic(fmt.Sprintf("immutable.defaultComparer: must set comparer for %T type", i)) +} + +// defaultCompare only operates on constraints.Ordered. +// For other types, users should bring their own comparers +func defaultCompare[K constraints.Ordered](i, j K) int { if i < j { return -1 } else if i > j { @@ -2360,11 +2394,11 @@ func (c *defaultComparer[K]) Compare(i K, j K) int { return 0 } -// reflectIntComparer compares two int values using reflection. Implements Comparer. -type reflectComparer[K constraints.Ordered] struct{} +// reflectIntComparer compares two values using reflection. Implements Comparer. +type reflectComparer[K comparable] struct{} // Compare returns -1 if a is less than b, returns 1 if a is greater than b, and -// returns 0 if a is equal to b. Panic if a or b is not an int. +// returns 0 if a is equal to b. Panic if a or b is not an int-ish or string-ish type. func (c *reflectComparer[K]) Compare(a, b K) int { switch reflect.TypeOf(a).Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: |