From 0ea29592931e161201af513af63b16cc2636e8db Mon Sep 17 00:00:00 2001 From: Fabio Scotto di Santolo Date: Tue, 10 Jan 2023 12:09:56 +0100 Subject: [PATCH] Implemented set and hashtable data structure --- pkg/collection/collection.go | 13 +++ pkg/collection/hashmap.go | 126 +++++++++++++++++++++++ pkg/collection/hashmap_test.go | 180 +++++++++++++++++++++++++++++++++ pkg/collection/hashset.go | 65 ++++++++++++ pkg/collection/hashset_test.go | 114 +++++++++++++++++++++ 5 files changed, 498 insertions(+) create mode 100644 pkg/collection/hashmap.go create mode 100644 pkg/collection/hashmap_test.go create mode 100644 pkg/collection/hashset.go create mode 100644 pkg/collection/hashset_test.go diff --git a/pkg/collection/collection.go b/pkg/collection/collection.go index 5dff2b6..0f62c78 100644 --- a/pkg/collection/collection.go +++ b/pkg/collection/collection.go @@ -34,3 +34,16 @@ type List[E any] interface { type Set[E comparable] interface { Collection[E] } + +type Map[K comparable, V any] interface { + Empty() bool + Size() int + Get(key K) (V, bool) + Put(key K, value V) + ContainsKey(key K) bool + ContainsValue(value V) bool + Delete(key K) bool + Keys() Set[K] + Values() Collection[V] + EntryList() Collection[*Entry[K, V]] +} diff --git a/pkg/collection/hashmap.go b/pkg/collection/hashmap.go new file mode 100644 index 0000000..a9707f3 --- /dev/null +++ b/pkg/collection/hashmap.go @@ -0,0 +1,126 @@ +package collection + +import ( + "fmt" + "reflect" + "strings" +) + +type HashMap[K comparable, V any] struct { + table map[K]V +} + +type Entry[K comparable, V any] struct { + key K + value V +} + +func NewEntry[K comparable, V any](key K, value V) *Entry[K, V] { + return &Entry[K, V]{ + key: key, + value: value, + } +} + +func (e Entry[K, V]) Key() K { + return e.key +} + +func (e Entry[K, V]) Value() V { + return e.value +} + +func (e Entry[K, V]) String() string { + var sb strings.Builder + sb.WriteString("[") + sb.WriteString(fmt.Sprintf("[%v, %v]", e.key, e.value)) + sb.WriteString("]") + return sb.String() +} + +func NewHashMap[K comparable, V any](entries ...*Entry[K, V]) *HashMap[K, V] { + m := make(map[K]V) + for _, e := range entries { + m[e.key] = e.value + } + return &HashMap[K, V]{m} +} + +func (h *HashMap[K, V]) Empty() bool { + return h.Size() == 0 +} + +func (h *HashMap[K, V]) Size() int { + return len(h.table) +} + +func (h *HashMap[K, V]) Get(key K) (V, bool) { + v, ok := h.table[key] + return v, ok +} + +func (h *HashMap[K, V]) Put(key K, value V) { + h.table[key] = value +} + +func (h *HashMap[K, V]) ContainsKey(key K) bool { + _, ok := h.table[key] + return ok +} + +func (h *HashMap[K, V]) ContainsValue(value V) bool { + for _, v := range h.table { + if reflect.DeepEqual(v, value) { + return true + } + } + return false +} + +func (h *HashMap[K, V]) Delete(key K) bool { + l := h.Size() + delete(h.table, key) + return l != h.Size() +} + +func (h *HashMap[K, V]) Keys() Set[K] { + set := NewHashSet[K]() + for k := range h.table { + set.Push(k) + } + return set +} + +func (h *HashMap[K, V]) Values() Collection[V] { + lst := NewSlice[V]() + for _, v := range h.table { + lst.PushBack(v) + } + return lst +} + +func (h *HashMap[K, V]) EntryList() Collection[*Entry[K, V]] { + lst := NewSlice[*Entry[K, V]]() + for k, v := range h.table { + lst.PushBack(NewEntry(k, v)) + } + return lst +} + +func (h *HashMap[K, V]) String() string { + var sb strings.Builder + sb.WriteString("{") + lst := h.EntryList() + for it := lst.Iterator(); it.HasNext(); { + var s string + i, item := it.NextWithIndex() + if i >= h.Size()-1 { + s = fmt.Sprintf("%v", item) + } else { + s = fmt.Sprintf("%v, ", item) + } + sb.WriteString(s) + } + sb.WriteString("}") + return sb.String() +} diff --git a/pkg/collection/hashmap_test.go b/pkg/collection/hashmap_test.go new file mode 100644 index 0000000..8f9b956 --- /dev/null +++ b/pkg/collection/hashmap_test.go @@ -0,0 +1,180 @@ +package collection + +import "testing" + +func TestHashMap_Empty(t *testing.T) { + useCases := []struct { + description string + table *HashMap[int, string] + want bool + }{ + {description: "empty hash map", table: NewHashMap[int, string](), want: true}, + {description: "hash map with one entry", table: NewHashMap[int, string](NewEntry(1, "hello there")), want: false}, + } + + for _, tt := range useCases { + result := tt.table.Empty() + if result != tt.want { + t.Errorf("test: %s want %v got %v", tt.description, tt.want, result) + } + } +} + +func TestHashMap_Size(t *testing.T) { + useCases := []struct { + description string + table *HashMap[int, string] + want int + }{ + {description: "empty hash map", table: NewHashMap[int, string](), want: 0}, + {description: "hash map with one entry", table: NewHashMap[int, string](NewEntry(1, "hello there")), want: 1}, + } + + for _, tt := range useCases { + result := tt.table.Size() + if result != tt.want { + t.Errorf("test: %s want %v got %v", tt.description, tt.want, result) + } + } +} + +func TestHashMap_Get(t *testing.T) { + useCases := []struct { + description string + table *HashMap[int, string] + key int + want string + }{ + {description: "Ask value by not found key", table: NewHashMap[int, string](), key: 1, want: ""}, + {description: "Ask value by found key", table: NewHashMap[int, string](NewEntry(1, "hello there")), key: 1, want: "hello there"}, + } + + for _, tt := range useCases { + result, _ := tt.table.Get(tt.key) + if result != tt.want { + t.Errorf("test: %s want %v got %v", tt.description, tt.want, result) + } + } +} + +func TestHashMap_Put(t *testing.T) { + useCases := []struct { + description string + table *HashMap[int, string] + entry *Entry[int, string] + want int + }{ + {description: "empty map put new pair", + table: NewHashMap[int, string](), + entry: NewEntry(1, "hello there"), + want: 1}, + {description: "put new pair in map with elements", + table: NewHashMap[int, string]( + NewEntry(1, "hello there"), + NewEntry(2, "welcome general Kenobi"), + ), + entry: NewEntry(3, "what's you name?"), + want: 3}, + } + + for _, tt := range useCases { + tt.table.Put(tt.entry.Key(), tt.entry.Value()) + if tt.table.Size() != tt.want { + t.Errorf("test: %s want %v got %v", tt.description, tt.want, tt.table.Size()) + } + } +} + +func TestHashMap_ContainsKey(t *testing.T) { + useCases := []struct { + description string + table *HashMap[int, string] + key int + want bool + }{ + {description: "Seek key not present in map", + table: NewHashMap[int, string](), + key: 1, + want: false}, + {description: "Seek key present in table", + table: NewHashMap[int, string]( + NewEntry(1, "hello there"), + ), + key: 1, + want: true}, + } + + for _, tt := range useCases { + result := tt.table.ContainsKey(tt.key) + if result != tt.want { + t.Errorf("test: %s want %v got %v", tt.description, tt.want, result) + } + } +} + +func TestHashMap_ContainsValue(t *testing.T) { + useCases := []struct { + description string + table *HashMap[int, string] + value string + want bool + }{ + {description: "Seek key not present in map", + table: NewHashMap[int, string](), + value: "hello there", + want: false}, + {description: "Seek key present in map", + table: NewHashMap[int, string]( + NewEntry(1, "hello there"), + NewEntry(2, "welcome general Kenobi"), + ), + value: "hello there", + want: true}, + } + + for _, tt := range useCases { + result := tt.table.ContainsValue(tt.value) + if result != tt.want { + t.Errorf("test: %s want %v got %v", tt.description, tt.want, result) + } + } +} + +func TestHashMap_Delete(t *testing.T) { + useCases := []struct { + description string + table *HashMap[int, string] + key int + want int + result bool + }{ + {description: "in empty map delete item is no-op action", + table: NewHashMap[int, string](), + key: 1, + want: 0, + result: false}, + {description: "in empty map delete item is no-op action", + table: NewHashMap[int, string]( + NewEntry(1, "hello there"), + ), + key: 1, + want: 0, + result: true}, + {description: "with three entry delete entry with key 1", + table: NewHashMap[int, string]( + NewEntry(1, "hello there"), + NewEntry(2, "welcome general Kenobi"), + NewEntry(3, "what's you name?"), + ), + key: 1, + want: 2, + result: true}, + } + + for _, tt := range useCases { + ok := tt.table.Delete(tt.key) + if tt.table.Size() != tt.want || ok != tt.result { + t.Errorf("test: %s want %v got %v", tt.description, tt.want, tt.table.Size()) + } + } +} diff --git a/pkg/collection/hashset.go b/pkg/collection/hashset.go new file mode 100644 index 0000000..55ba665 --- /dev/null +++ b/pkg/collection/hashset.go @@ -0,0 +1,65 @@ +package collection + +import ( + "fmt" + "strings" +) + +type HashSet[E comparable] struct { + inner map[E]bool +} + +func NewHashSet[E comparable](items ...E) *HashSet[E] { + inner := make(map[E]bool) + for _, item := range items { + inner[item] = true + } + return &HashSet[E]{inner} +} + +func (h *HashSet[E]) Iterator() Iterator[E] { + // FIXME this is only hack for iterate over map + items := NewSlice[E]() + for k := range h.inner { + items.Push(k) + } + return items.Iterator() +} + +func (h *HashSet[E]) Empty() bool { + return h.Size() == 0 +} + +func (h *HashSet[E]) Size() int { + return len(h.inner) +} + +func (h *HashSet[E]) Push(item E) { + h.inner[item] = true +} + +func (h *HashSet[E]) Contains(item E) bool { + return h.inner[item] +} + +func (h *HashSet[E]) Delete(item E) error { + delete(h.inner, item) + return nil +} + +func (h *HashSet[E]) String() string { + var sb strings.Builder + sb.WriteString("[") + for it := h.Iterator(); it.HasNext(); { + var s string + i, item := it.NextWithIndex() + if i >= h.Size()-1 { + s = fmt.Sprintf("%v", item) + } else { + s = fmt.Sprintf("%v, ", item) + } + sb.WriteString(s) + } + sb.WriteString("]") + return sb.String() +} diff --git a/pkg/collection/hashset_test.go b/pkg/collection/hashset_test.go new file mode 100644 index 0000000..5843d3b --- /dev/null +++ b/pkg/collection/hashset_test.go @@ -0,0 +1,114 @@ +package collection + +import "testing" + +func TestHashSet_Empty(t *testing.T) { + useCases := []struct { + description string + set *HashSet[int] + want bool + }{ + {description: "empty set", set: NewHashSet[int](), want: true}, + {description: "set with items", set: NewHashSet[int](1, 2, 3), want: false}, + } + + for _, tt := range useCases { + result := tt.set.Empty() + if result != tt.want { + t.Errorf("test: %s want %v got %v", tt.description, tt.want, result) + } + } +} + +func TestHashSet_Size(t *testing.T) { + useCases := []struct { + description string + set *HashSet[int] + want int + }{ + {description: "empty set", set: NewHashSet[int](), want: 0}, + {description: "set with items", set: NewHashSet[int](1, 2, 3), want: 3}, + } + + for _, tt := range useCases { + result := tt.set.Size() + if result != tt.want { + t.Errorf("test: %s want %v got %v", tt.description, tt.want, result) + } + } +} + +func TestHashSet_Push(t *testing.T) { + useCases := []struct { + description string + original *HashSet[int] + modified *HashSet[int] + item int + }{ + {description: "empty set push new item", + original: NewHashSet[int](), + modified: NewHashSet[int](1), + item: 1}, + {description: "set with items push new item", + original: NewHashSet[int](1, 2), + modified: NewHashSet[int](1, 2, 3), + item: 3}, + } + + for _, tt := range useCases { + tt.original.Push(tt.item) + if tt.original.Size() != tt.modified.Size() { + t.Errorf("test: %s want %v got %v", tt.description, tt.modified, tt.original) + } + } +} + +func TestHashSet_Contains(t *testing.T) { + useCases := []struct { + description string + set *HashSet[int] + item int + want bool + }{ + {description: "empty set", set: NewHashSet[int](), item: 0, want: false}, + {description: "set with items seek item not in set", set: NewHashSet[int](1, 2, 3), item: 0, want: false}, + {description: "set with items seek item in set", set: NewHashSet[int](1, 2, 3), item: 1, want: true}, + } + + for _, tt := range useCases { + result := tt.set.Contains(tt.item) + if result != tt.want { + t.Errorf("test: %s want %v got %v", tt.description, tt.want, result) + } + } +} + +func TestHashSet_Delete(t *testing.T) { + useCases := []struct { + description string + original *HashSet[int] + modified *HashSet[int] + item int + err error + }{ + {description: "singleton set delete item", + original: NewHashSet[int](1), + modified: NewHashSet[int](), + item: 1}, + {description: "set with items delete item", + original: NewHashSet[int](1, 2, 3), + modified: NewHashSet[int](1, 2), + item: 3}, + {description: "set with items delete item not in set", + original: NewHashSet[int](1, 2, 3), + modified: NewHashSet[int](1, 2, 3), + item: 9}, + } + + for _, tt := range useCases { + err := tt.original.Delete(tt.item) + if tt.original.Size() != tt.modified.Size() || err != tt.err { + t.Errorf("test: %s want %v got %v", tt.description, tt.modified, tt.original) + } + } +}