github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/x/generics/hashmap/map.go (about) 1 // Copyright (c) 2018 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package hashmap 22 23 import ( 24 "github.com/mauricelam/genny/generic" 25 ) 26 27 // KeyType is the generic key type for use with the specialized hash map. 28 type KeyType generic.Type 29 30 // ValueType is the generic value type for use with the specialized hash map. 31 type ValueType generic.Type 32 33 // MapHash is the hash for a given map entry, this is public to support 34 // iterating over the map using a native Go for loop. 35 type MapHash uint64 36 37 // HashFn is the hash function to execute when hashing a key. 38 type HashFn func(KeyType) MapHash 39 40 // EqualsFn is the equals key function to execute when detecting equality of a key. 41 type EqualsFn func(KeyType, KeyType) bool 42 43 // CopyFn is the copy key function to execute when copying the key. 44 type CopyFn func(KeyType) KeyType 45 46 // FinalizeFn is the finalize key function to execute when finished with a key. 47 type FinalizeFn func(KeyType) 48 49 // Map uses the genny package to provide a generic hash map that can be specialized 50 // by running the following command from this root of the repository: 51 // ``` 52 // make hashmap-gen pkg=outpkg key_type=Type value_type=Type out_dir=/tmp 53 // ``` 54 // Or if you would like to use bytes or ident.ID as keys you can use the 55 // partially specialized maps to generate your own maps as well: 56 // ``` 57 // make byteshashmap-gen pkg=outpkg value_type=Type out_dir=/tmp 58 // make idhashmap-gen pkg=outpkg value_type=Type out_dir=/tmp 59 // ``` 60 // This will output to stdout the generated source file to use for your map. 61 // It uses linear probing by incrementing the number of the hash created when 62 // hashing the identifier if there is a collision. 63 // Map is a value type and not an interface to allow for less painful 64 // upgrades when adding/removing methods, it is not likely to need mocking so 65 // an interface would not be super useful either. 66 type Map struct { 67 mapOptions 68 69 // lookup uses hash of the identifier for the key and the MapEntry value 70 // wraps the value type and the key (used to ensure lookup is correct 71 // when dealing with collisions), we use uint64 for the hash partially 72 // because lookups of maps with uint64 keys has a fast path for Go. 73 lookup map[MapHash]MapEntry 74 } 75 76 // mapOptions is a set of options used when creating an identifier map, it is kept 77 // private so that implementers of the generated map can specify their own options 78 // that partially fulfill these options. 79 type mapOptions struct { 80 // hash is the hash function to execute when hashing a key. 81 hash HashFn 82 // equals is the equals key function to execute when detecting equality. 83 equals EqualsFn 84 // copy is the copy key function to execute when copying the key. 85 copy CopyFn 86 // finalize is the finalize key function to execute when finished with a 87 // key, this is optional to specify. 88 finalize FinalizeFn 89 // initialSize is the initial size for the map, use zero to use Go's std map 90 // initial size and consequently is optional to specify. 91 initialSize int 92 } 93 94 // MapEntry is an entry in the map, this is public to support iterating 95 // over the map using a native Go for loop. 96 type MapEntry struct { 97 // key is used to check equality on lookups to resolve collisions 98 key mapKey 99 // value type stored 100 value ValueType 101 } 102 103 type mapKey struct { 104 key KeyType 105 finalize bool 106 } 107 108 // Key returns the map entry key. 109 func (e MapEntry) Key() KeyType { 110 return e.key.key 111 } 112 113 // Value returns the map entry value. 114 func (e MapEntry) Value() ValueType { 115 return e.value 116 } 117 118 // mapAlloc is a non-exported function so that when generating the source code 119 // for the map you can supply a public constructor that sets the correct 120 // hash, equals, copy, finalize options without users of the map needing to 121 // implement them themselves. 122 // nolint: deadcode 123 func mapAlloc(opts mapOptions) *Map { 124 m := &Map{mapOptions: opts} 125 m.Reallocate() 126 return m 127 } 128 129 func (m *Map) newMapKey(k KeyType, opts mapKeyOptions) mapKey { 130 key := mapKey{key: k, finalize: opts.finalizeKey} 131 if !opts.copyKey { 132 return key 133 } 134 135 key.key = m.copy(k) 136 return key 137 } 138 139 func (m *Map) removeMapKey(hash MapHash, key mapKey) { 140 delete(m.lookup, hash) 141 if key.finalize { 142 m.finalize(key.key) 143 } 144 } 145 146 // Get returns a value in the map for an identifier if found. 147 func (m *Map) Get(k KeyType) (ValueType, bool) { 148 hash := m.hash(k) 149 for entry, ok := m.lookup[hash]; ok; entry, ok = m.lookup[hash] { 150 if m.equals(entry.key.key, k) { 151 return entry.value, true 152 } 153 // Linear probe to "next" to this entry (really a rehash) 154 hash++ 155 } 156 var empty ValueType 157 return empty, false 158 } 159 160 // Set will set the value for an identifier. 161 func (m *Map) Set(k KeyType, v ValueType) { 162 m.set(k, v, mapKeyOptions{ 163 copyKey: true, 164 finalizeKey: m.finalize != nil, 165 }) 166 } 167 168 // SetUnsafeOptions is a set of options to use when setting a value with 169 // the SetUnsafe method. 170 type SetUnsafeOptions struct { 171 NoCopyKey bool 172 NoFinalizeKey bool 173 } 174 175 // SetUnsafe will set the value for an identifier with unsafe options for how 176 // the map treats the key. 177 func (m *Map) SetUnsafe(k KeyType, v ValueType, opts SetUnsafeOptions) { 178 m.set(k, v, mapKeyOptions{ 179 copyKey: !opts.NoCopyKey, 180 finalizeKey: !opts.NoFinalizeKey, 181 }) 182 } 183 184 type mapKeyOptions struct { 185 copyKey bool 186 finalizeKey bool 187 } 188 189 func (m *Map) set(k KeyType, v ValueType, opts mapKeyOptions) { 190 hash := m.hash(k) 191 for entry, ok := m.lookup[hash]; ok; entry, ok = m.lookup[hash] { 192 if m.equals(entry.key.key, k) { 193 m.lookup[hash] = MapEntry{ 194 key: entry.key, 195 value: v, 196 } 197 return 198 } 199 // Linear probe to "next" to this entry (really a rehash) 200 hash++ 201 } 202 203 m.lookup[hash] = MapEntry{ 204 key: m.newMapKey(k, opts), 205 value: v, 206 } 207 } 208 209 // Iter provides the underlying map to allow for using a native Go for loop 210 // to iterate the map, however callers should only ever read and not write 211 // the map. 212 func (m *Map) Iter() map[MapHash]MapEntry { 213 return m.lookup 214 } 215 216 // Len returns the number of map entries in the map. 217 func (m *Map) Len() int { 218 return len(m.lookup) 219 } 220 221 // Contains returns true if value exists for key, false otherwise, it is 222 // shorthand for a call to Get that doesn't return the value. 223 func (m *Map) Contains(k KeyType) bool { 224 _, ok := m.Get(k) 225 return ok 226 } 227 228 // Delete will remove a value set in the map for the specified key. 229 func (m *Map) Delete(k KeyType) { 230 hash := m.hash(k) 231 for entry, ok := m.lookup[hash]; ok; entry, ok = m.lookup[hash] { 232 if m.equals(entry.key.key, k) { 233 m.removeMapKey(hash, entry.key) 234 return 235 } 236 // Linear probe to "next" to this entry (really a rehash) 237 hash++ 238 } 239 } 240 241 // Reset will reset the map by simply deleting all keys to avoid 242 // allocating a new map. 243 func (m *Map) Reset() { 244 for hash, entry := range m.lookup { 245 m.removeMapKey(hash, entry.key) 246 } 247 } 248 249 // Reallocate will avoid deleting all keys and reallocate a new 250 // map, this is useful if you believe you have a large map and 251 // will not need to grow back to a similar size. 252 func (m *Map) Reallocate() { 253 if m.initialSize > 0 { 254 m.lookup = make(map[MapHash]MapEntry, m.initialSize) 255 } else { 256 m.lookup = make(map[MapHash]MapEntry) 257 } 258 }