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  }