From 84d06a186e67db357dac81841a0b79ff5db3caff Mon Sep 17 00:00:00 2001 From: Theodoros Symeonidis Date: Sun, 2 Jul 2023 18:08:53 +0300 Subject: [PATCH 1/7] Update the implementation of HashMap to use separate arrays for Keys, Values and Filled Elements. --- hashmap/map.go | 111 ++++++++++++++++++++++++++++++------------------- hashset/set.go | 4 ++ 2 files changed, 73 insertions(+), 42 deletions(-) diff --git a/hashmap/map.go b/hashmap/map.go index 088978a..2078ec2 100644 --- a/hashmap/map.go +++ b/hashmap/map.go @@ -10,15 +10,11 @@ import ( g "github.com/zyedidia/generic" ) -type entry[K, V any] struct { - key K - filled bool - value V -} - // A Map is a hashmap that supports copying via copy-on-write. type Map[K, V any] struct { - entries []entry[K, V] + keys []K + values []V + filled []bool capacity uint64 length uint64 readonly bool @@ -46,7 +42,9 @@ func New[K, V any](capacity uint64, equals g.EqualsFn[K], hash g.HashFn[K]) *Map } capacity = pow2ceil(capacity) return &Map[K, V]{ - entries: make([]entry[K, V], capacity), + keys: make([]K, capacity), + values: make([]V, capacity), + filled: make([]bool, capacity), capacity: capacity, ops: ops[K]{ equals: equals, @@ -61,9 +59,9 @@ func (m *Map[K, V]) Get(key K) (V, bool) { hash := m.ops.hash(key) idx := hash & (m.capacity - 1) - for m.entries[idx].filled { - if m.ops.equals(m.entries[idx].key, key) { - return m.entries[idx].value, true + for m.filled[idx] { + if m.ops.equals(m.keys[idx], key) { + return m.values[idx], true } idx++ if idx >= m.capacity { @@ -79,17 +77,22 @@ func (m *Map[K, V]) resize(newcap uint64) { newm := Map[K, V]{ capacity: newcap, length: m.length, - entries: make([]entry[K, V], newcap), + keys: make([]K, newcap), + values: make([]V, newcap), + filled: make([]bool, newcap), ops: m.ops, } - for _, ent := range m.entries { - if ent.filled { - newm.Put(ent.key, ent.value) + for i := range m.keys { + if m.filled[i] { + newm.Put(m.keys[i], m.values[i]) } } + m.capacity = newm.capacity - m.entries = newm.entries + m.keys = newm.keys + m.values = newm.values + m.filled = newm.filled } // Put maps the given key to the given value. If the key already exists its @@ -98,18 +101,24 @@ func (m *Map[K, V]) Put(key K, val V) { if m.length >= m.capacity/2 { m.resize(m.capacity * 2) } else if m.readonly { - entries := make([]entry[K, V], len(m.entries), cap(m.entries)) - copy(entries, m.entries) - m.entries = entries + keys := make([]K, len(m.keys), cap(m.keys)) + values := make([]V, len(m.values), cap(m.values)) + filled := make([]bool, len(m.filled), cap(m.filled)) + copy(keys, m.keys) + copy(values, m.values) + copy(filled, m.filled) + m.keys = keys + m.values = values + m.filled = filled m.readonly = false } hash := m.ops.hash(key) idx := hash & (m.capacity - 1) - for m.entries[idx].filled { - if m.ops.equals(m.entries[idx].key, key) { - m.entries[idx].value = val + for m.filled[idx] { + if m.ops.equals(m.keys[idx], key) { + m.values[idx] = val return } idx++ @@ -118,18 +127,18 @@ func (m *Map[K, V]) Put(key K, val V) { } } - m.entries[idx].key = key - m.entries[idx].value = val - m.entries[idx].filled = true + m.keys[idx] = key + m.values[idx] = val + m.filled[idx] = true m.length++ } func (m *Map[K, V]) remove(idx uint64) { var k K var v V - m.entries[idx].filled = false - m.entries[idx].key = k - m.entries[idx].value = v + m.filled[idx] = false + m.keys[idx] = k + m.values[idx] = v m.length-- } @@ -138,27 +147,33 @@ func (m *Map[K, V]) Remove(key K) { hash := m.ops.hash(key) idx := hash & (m.capacity - 1) - for m.entries[idx].filled && !m.ops.equals(m.entries[idx].key, key) { + for m.filled[idx] && !m.ops.equals(m.keys[idx], key) { idx = (idx + 1) & (m.capacity - 1) } - if !m.entries[idx].filled { + if !m.filled[idx] { return } if m.readonly { - entries := make([]entry[K, V], len(m.entries), cap(m.entries)) - copy(entries, m.entries) - m.entries = entries + keys := make([]K, len(m.keys), cap(m.keys)) + values := make([]V, len(m.values), cap(m.values)) + filled := make([]bool, len(m.filled), cap(m.filled)) + copy(keys, m.keys) + copy(values, m.values) + copy(filled, m.filled) + m.keys = keys + m.values = values + m.filled = filled m.readonly = false } m.remove(idx) idx = (idx + 1) & (m.capacity - 1) - for m.entries[idx].filled { - krehash := m.entries[idx].key - vrehash := m.entries[idx].value + for m.filled[idx] { + krehash := m.keys[idx] + vrehash := m.values[idx] m.remove(idx) m.Put(krehash, vrehash) idx = (idx + 1) & (m.capacity - 1) @@ -172,8 +187,8 @@ func (m *Map[K, V]) Remove(key K) { // Clear removes all key-value pairs from the map. func (m *Map[K, V]) Clear() { - for idx, entry := range m.entries { - if entry.filled { + for idx := range m.keys { + if m.filled[idx] { m.remove(uint64(idx)) } } @@ -190,7 +205,9 @@ func (m *Map[K, V]) Size() int { func (m *Map[K, V]) Copy() *Map[K, V] { m.readonly = true return &Map[K, V]{ - entries: m.entries, + keys: m.keys, + values: m.values, + filled: m.filled, capacity: m.capacity, length: m.length, readonly: true, @@ -201,9 +218,19 @@ func (m *Map[K, V]) Copy() *Map[K, V] { // Each calls 'fn' on every key-value pair in the hashmap in no particular // order. func (m *Map[K, V]) Each(fn func(key K, val V)) { - for _, ent := range m.entries { - if ent.filled { - fn(ent.key, ent.value) + for idx := range m.keys { + if m.filled[idx] { + fn(m.keys[idx], m.values[idx]) } } } + +// Keys returns the key of the hashmap +func (m *Map[K, V]) Keys() []K { + return m.keys +} + +// Values returns the values of the hashmap +func (m *Map[K, V]) Values() []V { + return m.values +} diff --git a/hashset/set.go b/hashset/set.go index 3bcaabb..8a4ab7c 100644 --- a/hashset/set.go +++ b/hashset/set.go @@ -66,3 +66,7 @@ func (s *Set[K]) Copy() *Set[K] { m: s.m.Copy(), } } + +func (s *Set[K]) Values() []K { + return s.m.Keys() +} From 9079f61b49e20e223e64266c1cb54f8df5b73632 Mon Sep 17 00:00:00 2001 From: Theodoros Symeonidis Date: Sun, 2 Jul 2023 19:21:35 +0300 Subject: [PATCH 2/7] Fix some naming issues on set.go file. --- set/set.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/set/set.go b/set/set.go index cb9cdf5..e91455e 100644 --- a/set/set.go +++ b/set/set.go @@ -70,9 +70,9 @@ func (s Set[K]) ConstUnion(with ...K) Set[K] { } func (s Set[K]) Clone() Set[K] { - new := NewSet(s.new) - s.Each(func(key K) { new.Put(key) }) - return new + news := NewSet(s.new) + s.Each(func(key K) { news.Put(key) }) + return news } func (s Set[K]) String() string { @@ -91,19 +91,19 @@ func (s Set[K]) Map() map[K]struct{} { } func (s Set[K]) SymmetricDifference(others ...SetOf[K]) Set[K] { - new := s.Clone() - seen := new.Clone() + news := s.Clone() + seen := news.Clone() for _, other := range others { other.Each(func(key K) { if seen.Has(key) { - new.Remove(key) + news.Remove(key) return } - new.Put(key) + news.Put(key) seen.Put(key) }) } - return new + return news } func (s Set[K]) InPlaceIntersection(others ...SetOf[K]) Set[K] { From affe6ea007f7cef4f58609e594740cd08ddf25e4 Mon Sep 17 00:00:00 2001 From: Theodoros Symeonidis Date: Mon, 3 Jul 2023 00:44:40 +0300 Subject: [PATCH 3/7] Update map.go to properly support Keys() and Values() methods. --- hashmap/map.go | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/hashmap/map.go b/hashmap/map.go index 2078ec2..df9ddde 100644 --- a/hashmap/map.go +++ b/hashmap/map.go @@ -227,10 +227,24 @@ func (m *Map[K, V]) Each(fn func(key K, val V)) { // Keys returns the key of the hashmap func (m *Map[K, V]) Keys() []K { - return m.keys + keys := make([]K, len(m.keys)) + + for idx := range m.keys { + if m.filled[idx] { + keys = append(keys, m.keys[idx]) + } + } + return keys[:m.Size()] } // Values returns the values of the hashmap func (m *Map[K, V]) Values() []V { - return m.values + values := make([]V, len(m.keys)) + + for idx := range m.keys { + if m.filled[idx] { + values = append(values, m.values[idx]) + } + } + return values[:m.Size()] } From d452327f279c2fc815faf1f72ca8e402f99382f2 Mon Sep 17 00:00:00 2001 From: Theodoros Symeonidis Date: Mon, 3 Jul 2023 00:45:01 +0300 Subject: [PATCH 4/7] Update set.go to add support for picking random elements. --- hashset/set.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/hashset/set.go b/hashset/set.go index 8a4ab7c..ce5988c 100644 --- a/hashset/set.go +++ b/hashset/set.go @@ -4,6 +4,7 @@ package hashset import ( g "github.com/zyedidia/generic" "github.com/zyedidia/generic/hashmap" + "math/rand" ) // Set implements a hashset, using the hashmap as the underlying storage. @@ -70,3 +71,7 @@ func (s *Set[K]) Copy() *Set[K] { func (s *Set[K]) Values() []K { return s.m.Keys() } + +func (s *Set[K]) Random() K { + return s.Values()[rand.Intn(s.Size())] +} From 86f0789e453249c96ab80c902c3ddd935404b56c Mon Sep 17 00:00:00 2001 From: Theodoros Symeonidis Date: Mon, 3 Jul 2023 00:48:20 +0300 Subject: [PATCH 5/7] Update map.go to add support for picking random elements. --- hashmap/map.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/hashmap/map.go b/hashmap/map.go index df9ddde..1561512 100644 --- a/hashmap/map.go +++ b/hashmap/map.go @@ -8,6 +8,7 @@ package hashmap import ( g "github.com/zyedidia/generic" + "math/rand" ) // A Map is a hashmap that supports copying via copy-on-write. @@ -248,3 +249,8 @@ func (m *Map[K, V]) Values() []V { } return values[:m.Size()] } + +func (m *Map[K, V]) Random() (K, V) { + randomIndex := rand.Intn(m.Size()) + return m.Keys()[randomIndex], m.Values()[randomIndex] +} From f762a59b90afeba4c8ec7db44e78812a56c7465b Mon Sep 17 00:00:00 2001 From: st1064870 <61602820+st1064870@users.noreply.github.com> Date: Mon, 3 Jul 2023 13:21:41 +0300 Subject: [PATCH 6/7] Change the module path in go.mod --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 90e6b35..132e685 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/zyedidia/generic +module github.com/st1064870/generic go 1.18 From e1bdbdd7fe45460297f415b52a7dbbdb06bf5f17 Mon Sep 17 00:00:00 2001 From: Theodoros Symeonidis Date: Mon, 3 Jul 2023 13:24:06 +0300 Subject: [PATCH 7/7] Change the module path in go.mod file. --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 90e6b35..132e685 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/zyedidia/generic +module github.com/st1064870/generic go 1.18