Skip to content

Commit

Permalink
feat: add hashmaps
Browse files Browse the repository at this point in the history
  • Loading branch information
Youssef ahmed committed Dec 21, 2023
1 parent 9ca3504 commit 598f765
Show file tree
Hide file tree
Showing 2 changed files with 236 additions and 0 deletions.
202 changes: 202 additions & 0 deletions hashmap/hashmap.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
package hashmap

import (
"hash/fnv"

"github.com/joetifa2003/mm-go"
"github.com/joetifa2003/mm-go/linkedlist"
"github.com/joetifa2003/mm-go/vector"
)

// Hashable keys must implement this interface
// or use type hashmap.String and hashmap.Int
// which implements the interface
type Hashable interface {
comparable
Hash() uint32
}

// String a string type that implements Hashable Interface
type String string

func (s String) Hash() uint32 {
h := fnv.New32a()
h.Write([]byte(s))
return h.Sum32()
}

// Int an int type that implements Hashable Interface
type Int int

func (i Int) Hash() uint32 {
return uint32(i)
}

type pair[K Hashable, V any] struct {
key K
value V
}

// Hashmap Manually managed hashmap,
// keys can be hashmap.String, hashmap.Int or any type that
// implements the hashmap.Hashable interface
type Hashmap[K Hashable, V any] struct {
pairs *vector.Vector[*linkedlist.LinkedList[pair[K, V]]]
totalTaken int
}

// New creates a new Hashmap with key of type K and value of type V
func New[K Hashable, V any]() *Hashmap[K, V] {
hm := mm.Alloc[Hashmap[K, V]]()
hm.pairs = vector.New[*linkedlist.LinkedList[pair[K, V]]](8)
return hm
}

func (hm *Hashmap[K, V]) extend() {
newPairs := vector.New[*linkedlist.LinkedList[pair[K, V]]](hm.pairs.Len() * 2)
oldPairs := hm.pairs
defer oldPairs.Free()

hm.totalTaken = 0
hm.pairs = newPairs

for _, pairs := range oldPairs.Slice() {
if pairs == nil {
continue
}

pairs.ForEach(func(idx int, p pair[K, V]) {
hm.Insert(p.key, p.value)
})
}
}

// Insert inserts a new value V if key K doesn't exist,
// Otherwise update the key K with value V
func (hm *Hashmap[K, V]) Insert(key K, value V) {
if ptr, exists := hm.GetPtr(key); exists {
*ptr = value
return
}

if hm.totalTaken == hm.pairs.Len() {
hm.extend()
}

idx := int(key.Hash() % uint32(hm.pairs.Len()))
pairs := hm.pairs.At(idx)
if pairs == nil {
newPairs := linkedlist.New[pair[K, V]]()
hm.pairs.Set(idx, newPairs)
pairs = newPairs
}
pairs.PushBack(pair[K, V]{key: key, value: value})
hm.totalTaken++
}

// Get takes key K and return value V
func (hm *Hashmap[K, V]) Get(key K) (value V, exists bool) {
idx := int(key.Hash() % uint32(hm.pairs.Len()))
pairs := hm.pairs.At(idx)
if pairs == nil {
return *new(V), false
}

pairIdx, ok := pairs.FindIndex(func(value pair[K, V]) bool {
return value.key == key
})
if !ok {
return *new(V), false
}

return pairs.At(pairIdx).value, ok
}

// GetPtr takes key K and return a pointer to value V
func (hm *Hashmap[K, V]) GetPtr(key K) (value *V, exists bool) {
idx := int(key.Hash() % uint32(hm.pairs.Len()))
pairs := hm.pairs.At(idx)
if pairs == nil {
return nil, false
}

pairIdx, ok := pairs.FindIndex(func(value pair[K, V]) bool {
return value.key == key
})
if !ok {
return nil, false
}

return &pairs.AtPtr(pairIdx).value, ok
}

// ForEach iterates through all key/value pairs
func (hm *Hashmap[K, V]) ForEach(f func(key K, value V)) {
for _, pairs := range hm.pairs.Slice() {
if pairs == nil {
continue
}

pairs.ForEach(func(idx int, p pair[K, V]) {
f(p.key, p.value)
})
}
}

// Values returns all values as a slice
func (hm *Hashmap[K, V]) Values() []V {
res := make([]V, 0)

for _, pairs := range hm.pairs.Slice() {
if pairs == nil {
continue
}

pairs.ForEach(func(idx int, p pair[K, V]) {
res = append(res, p.value)
})
}

return res
}

// Keys returns all keys as a slice
func (hm *Hashmap[K, V]) Keys() []K {
res := make([]K, 0)

for _, pairs := range hm.pairs.Slice() {
if pairs == nil {
continue
}

pairs.ForEach(func(idx int, p pair[K, V]) {
res = append(res, p.key)
})
}

return res
}

// Delete delete value with key K
func (hm *Hashmap[K, V]) Delete(key K) {
idx := int(key.Hash() % uint32(hm.pairs.Len()))
pairs := hm.pairs.At(idx)
if pairs == nil {
return
}

pairs.Remove(func(idx int, p pair[K, V]) bool {
return p.key == key
})
}

// Free frees the Hashmap
func (hm *Hashmap[K, V]) Free() {
for _, pairs := range hm.pairs.Slice() {
if pairs != nil {
pairs.Free()
}
}
hm.pairs.Free()
mm.Free(hm)
}
34 changes: 34 additions & 0 deletions hashmap/hashmap_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package hashmap_test

import (
"fmt"
"runtime"
"testing"

"github.com/joetifa2003/mm-go/hashmap"
)

const TIMES = 5000

func BenchmarkHashmap(b *testing.B) {
for i := 0; i < b.N; i++ {
h := hashmap.New[hashmap.Int, int]()

for i := 0; i < TIMES; i++ {
h.Insert(hashmap.Int(i), i)
}
h.Free()
runtime.GC()
}
}

func BenchmarkHashmapGo(b *testing.B) {
for i := 0; i < b.N; i++ {
h := map[string]int{}
for i := 0; i < TIMES; i++ {
h[fmt.Sprint(i)] = i
}
_ = h
runtime.GC()
}
}

0 comments on commit 598f765

Please sign in to comment.