Implemented set and hashtable data structure

This commit is contained in:
Fabio Scotto di Santolo
2023-01-10 12:09:56 +01:00
parent 43a5c8b244
commit 0ea2959293
5 changed files with 498 additions and 0 deletions

View File

@@ -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]]
}

126
pkg/collection/hashmap.go Normal file
View File

@@ -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()
}

View File

@@ -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())
}
}
}

65
pkg/collection/hashset.go Normal file
View File

@@ -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()
}

View File

@@ -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)
}
}
}