github.com/iainanderson83/datastructures@v0.0.4-0.20191103204413-889e20b53bcf/hashmap/hashmap.go (about)

     1  // runtime.memhash implementation taken from:
     2  // https://github.com/dgraph-io/ristretto/blob/master/z/rtutil.go
     3  // License can be found:
     4  // https://raw.githubusercontent.com/dgraph-io/ristretto/master/LICENSE
     5  
     6  package hashmap
     7  
     8  import (
     9  	"fmt"
    10  	"strings"
    11  	"sync/atomic"
    12  
    13  	"github.com/cespare/xxhash"
    14  	"github.com/segmentio/fasthash/fnv1a"
    15  )
    16  
    17  var (
    18  	// make them var for tests
    19  	loadFactor = 0.75 // if this goes below 0.5 the bounds won't work
    20  	length     = 3
    21  )
    22  
    23  type entry struct {
    24  	hash  uint64
    25  	key   string
    26  	value interface{}
    27  }
    28  
    29  // Hashmap is a naive implementation of a hashmap struct.
    30  type Hashmap struct {
    31  	lbound int
    32  	ubound int
    33  	length int
    34  	fn     func(string) uint64
    35  
    36  	lock    uintptr
    37  	buckets [][8]entry
    38  }
    39  
    40  // NewFNV1aHashmap returns a hashmap using the fnv1a
    41  // hashing function.
    42  func NewFNV1aHashmap() *Hashmap {
    43  	return NewHashmap(fnv1a.HashString64)
    44  }
    45  
    46  // NewXXHashmap returns a hashmap using the xxhash
    47  // hashing function.
    48  func NewXXHashmap() *Hashmap {
    49  	return NewHashmap(xxhash.Sum64String)
    50  }
    51  
    52  // NewHashmap creates a new, empty, hashmap.
    53  func NewHashmap(fn func(string) uint64) *Hashmap {
    54  	return newWithCap(fn, 1<<length)
    55  }
    56  
    57  func newWithCap(fn func(string) uint64, cap int) *Hashmap {
    58  	h := &Hashmap{
    59  		lbound:  int(float64(int(1)<<length) * (1 - loadFactor)),
    60  		ubound:  int(float64(int(1)<<length) * loadFactor),
    61  		length:  cap,
    62  		buckets: make([][8]entry, cap),
    63  		fn:      fn,
    64  	}
    65  
    66  	if h.lbound == 1<<length || h.ubound == 1<<length {
    67  		panic("invalid load factor")
    68  	}
    69  
    70  	return h
    71  }
    72  
    73  // Add inserts the value v associated with the key k into the hashmap.
    74  // Redistribution of keys occurs if load factor is surpassed.
    75  func (h *Hashmap) Add(k string, v interface{}) bool {
    76  	for {
    77  		if atomic.CompareAndSwapUintptr(&h.lock, 0, 1) {
    78  			break
    79  		}
    80  	}
    81  
    82  	hash := h.fn(k)
    83  	idx := hash & (uint64(h.length) - 1)
    84  
    85  	var (
    86  		exists bool
    87  		length int
    88  		target = -1
    89  	)
    90  	for i := range h.buckets[idx] {
    91  		if h.buckets[idx][i].hash == hash {
    92  			h.buckets[idx][i].value = v
    93  			exists = true
    94  		}
    95  
    96  		if h.buckets[idx][i].hash <= 0 {
    97  			if target == -1 {
    98  				target = i
    99  			}
   100  		} else {
   101  			length++
   102  		}
   103  	}
   104  
   105  	if !exists {
   106  		if target == -1 {
   107  			target = length
   108  		}
   109  		h.buckets[idx][target].hash = hash
   110  		h.buckets[idx][target].key = k
   111  		h.buckets[idx][target].value = v
   112  	}
   113  
   114  	// Assume even distribution
   115  	if length >= h.ubound {
   116  		h.length *= 2
   117  		h.resize()
   118  	}
   119  
   120  	atomic.StoreUintptr(&h.lock, 0)
   121  	return !exists
   122  }
   123  
   124  // Delete removes the key from the map, if it exists,
   125  // and returns whether or not it was deleted.
   126  func (h *Hashmap) Delete(k string) bool {
   127  	for {
   128  		if atomic.CompareAndSwapUintptr(&h.lock, 0, 1) {
   129  			break
   130  		}
   131  	}
   132  
   133  	hash := h.fn(k)
   134  	idx := hash & (uint64(h.length) - 1)
   135  
   136  	var (
   137  		exists bool
   138  		length int
   139  	)
   140  	for i := range h.buckets[idx] {
   141  		if h.buckets[idx][i].hash == hash {
   142  			h.buckets[idx][i].hash = 0
   143  			h.buckets[idx][i].key = ""
   144  			h.buckets[idx][i].value = nil
   145  			exists = true
   146  		}
   147  
   148  		if h.buckets[idx][i].hash > 0 {
   149  			length++
   150  		}
   151  	}
   152  
   153  	if length <= h.lbound {
   154  		h.length /= 2
   155  		h.resize()
   156  	}
   157  
   158  	atomic.StoreUintptr(&h.lock, 0)
   159  	return exists
   160  }
   161  
   162  // Lookup will try to retrieve the value associated with
   163  // the specified key.
   164  func (h *Hashmap) Lookup(k string) (interface{}, bool) {
   165  	for {
   166  		if atomic.CompareAndSwapUintptr(&h.lock, 0, 1) {
   167  			break
   168  		}
   169  	}
   170  
   171  	hash := h.fn(k)
   172  	idx := hash & (uint64(h.length) - 1)
   173  
   174  	for i := range h.buckets[idx] {
   175  		if h.buckets[idx][i].hash == hash {
   176  			atomic.StoreUintptr(&h.lock, 0)
   177  			return h.buckets[idx][i].value, true
   178  		}
   179  	}
   180  
   181  	atomic.StoreUintptr(&h.lock, 0)
   182  	return nil, false
   183  }
   184  
   185  // Iter calls the provided cb for each key/value pair in the map.
   186  func (h *Hashmap) Iter(fn func(k string, v interface{}) bool) {
   187  	if fn == nil {
   188  		return
   189  	}
   190  
   191  	for {
   192  		if atomic.CompareAndSwapUintptr(&h.lock, 0, 1) {
   193  			break
   194  		}
   195  	}
   196  
   197  	for i := range h.buckets {
   198  		for j := range h.buckets[i] {
   199  			if !fn(h.buckets[i][j].key, h.buckets[i][j].value) {
   200  				atomic.StoreUintptr(&h.lock, 0)
   201  				return
   202  			}
   203  		}
   204  	}
   205  
   206  	atomic.StoreUintptr(&h.lock, 0)
   207  }
   208  
   209  // Len returns the number of elements in the map.
   210  func (h *Hashmap) Len() int {
   211  	for {
   212  		if atomic.CompareAndSwapUintptr(&h.lock, 0, 1) {
   213  			break
   214  		}
   215  	}
   216  
   217  	var length int
   218  	for i := range h.buckets {
   219  		for j := range h.buckets[i] {
   220  			if h.buckets[i][j].hash > 0 {
   221  				length++
   222  			}
   223  		}
   224  	}
   225  
   226  	atomic.StoreUintptr(&h.lock, 0)
   227  	return length
   228  }
   229  
   230  func (h *Hashmap) resize() {
   231  	buckets := make([][8]entry, h.length)
   232  
   233  	for i := range h.buckets {
   234  		var length int
   235  		for j := range h.buckets[i] {
   236  			if h.buckets[i][j].hash == 0 {
   237  				continue
   238  			}
   239  			idx := h.buckets[i][j].hash & (uint64(h.length) - 1)
   240  
   241  			buckets[idx][length] = h.buckets[i][j]
   242  			length++
   243  		}
   244  	}
   245  
   246  	h.buckets = buckets
   247  }
   248  
   249  func spew(buckets [][8]entry) string {
   250  	var lengths []int
   251  	for i := range buckets {
   252  		var length int
   253  		for j := range buckets[i] {
   254  			if buckets[i][j].hash != 0 {
   255  				length++
   256  			}
   257  		}
   258  		lengths = append(lengths, int(length))
   259  	}
   260  
   261  	var b strings.Builder
   262  	for i := range lengths {
   263  		b.WriteString(fmt.Sprintf("[%d]", lengths[i]))
   264  	}
   265  	return b.String()
   266  }