Implemented set and hashtable data structure
This commit is contained in:
@@ -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
126
pkg/collection/hashmap.go
Normal 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()
|
||||
}
|
||||
180
pkg/collection/hashmap_test.go
Normal file
180
pkg/collection/hashmap_test.go
Normal 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
65
pkg/collection/hashset.go
Normal 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()
|
||||
}
|
||||
114
pkg/collection/hashset_test.go
Normal file
114
pkg/collection/hashset_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user