Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] v0.2.13 release candidate #10

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
177 changes: 177 additions & 0 deletions containers/linked_hash_map/linked_hash_map.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
// Copyright 2021. The GTL Authors. All rights reserved.
// https://github.com/modern-dev/gtl
// Use of this source code is governed by the MIT
// license that can be found in the LICENSE file.

package linked_hash_map

import (
"github.com/modern-dev/gtl"
)

type (
// LinkedHashMap TODO
LinkedHashMap[TKey comparable, TVal any] struct {
store map[TKey][]*node[TVal]
head, tail *node[TVal]
dupl bool
size int
}

node[TVal any] struct {
val TVal
prev, next *node[TVal]
}
)

// NewLinkedHashMap TODO
func NewLinkedHashMap[TKey comparable, TVal any](allowDuplicates bool) *LinkedHashMap[TKey, TVal] {
head, tail := &node[TVal]{}, &node[TVal]{}
head.next = tail
tail.prev = head

return &LinkedHashMap[TKey, TVal]{
make(map[TKey][]*node[TVal]),
head,
tail,
allowDuplicates,
0,
}
}

/* Capacity members */

// Empty checks if the container has no elements.
// Complexity — constant.
func (lhm *LinkedHashMap[TKey, TVal]) Empty() bool {
return lhm.Size() == 0
}

// Size returns the number of elements in the container.
// Complexity — constant.
func (lhm *LinkedHashMap[TKey, TVal]) Size() int {
return lhm.size
}

/* Modifiers members */

// Clear erases all elements from the container. After this call, Size() returns zero.
// Complexity — constant.
func (lhm *LinkedHashMap[TKey, TVal]) Clear() {
lhm.head.next = lhm.tail
lhm.tail.prev = lhm.head
lhm.store = make(map[TKey][]*node[TVal])
lhm.size = 0
}

// Insert inserts element into the container.
// Complexity — average case - O(1), worst case - O(size()).
func (lhm *LinkedHashMap[TKey, TVal]) Insert(key TKey, val TVal) bool {
if _, found := lhm.store[key]; found && !lhm.dupl {
return false
}

newNode := &node[TVal]{
val,
nil,
nil,
}

lhm.store[key] = append(lhm.store[key], newNode)
lhm.size++

lhm.appendNode(newNode)

return true
}

// Assign assigns to the current element if the key already exists.
// Throws gtl.OutOfRange if the container does not have an element with the specified key.
// Complexity — constant.
func (lhm *LinkedHashMap[TKey, TVal]) Assign(key TKey, val TVal) {
lhm.getNode(key, 0).val = val
}

// InsertOrAssign inserts an element or assigns to the current element if the key already exists.
// Returns true if the insertion took place and false if the assignment took place.
// Complexity — average case - O(1), worst case - O(Size()).
func (lhm *LinkedHashMap[TKey, TVal]) InsertOrAssign(key TKey, val TVal) bool {
if lhm.Contains(key) {
lhm.Assign(key, val)

return false
}

return lhm.Insert(key, val)
}

// Erase removes all elements with the key equivalent to key.
// Complexity — average case - O(1), worst case - O(Size()).
func (lhm *LinkedHashMap[TKey, TVal]) Erase(key TKey) {
if _, found := lhm.store[key]; !found {
return
}

for _, remNode := range lhm.store[key] {
lhm.removeNode(remNode)
}

lhm.size -= len(lhm.store[key])

delete(lhm.store, key)
}

/* Lookup members */

// At returns a reference to the mapped value of the element with key equivalent to key.
// If no such element exists, a panic of type gtl.OutOfRange is thrown.
// Complexity — constant.
func (lhm *LinkedHashMap[TKey, TVal]) At(key TKey) TVal {
return lhm.getNode(key, 0).val
}

// Count returns the number of elements with key that compares equal to the specified argument key.
// Complexity — linear in the number of elements with key on average, worst case linear in the size of the container.
func (lhm *LinkedHashMap[TKey, TVal]) Count(key TKey) int {
items, found := lhm.store[key]

if !found {
return 0
}

return len(items)
}

// Contains checks if there is an element with key equivalent to key in the container.
// Complexity — constant.
func (lhm *LinkedHashMap[TKey, TVal]) Contains(key TKey) bool {
_, found := lhm.store[key]

return found
}

/* Private members */

func (lhm *LinkedHashMap[TKey, TVal]) appendNode(newNode *node[TVal]) {
next := lhm.head.next

next.prev = newNode
newNode.next = next
lhm.head.next = newNode
newNode.prev = lhm.head
}

func (lhm *LinkedHashMap[TKey, TVal]) removeNode(remNode *node[TVal]) {
prev, next := remNode.prev, remNode.next

prev.next = next
next.prev = prev
}

func (lhm *LinkedHashMap[TKey, TVal]) getNode(key TKey, idx int) *node[TVal] {
if items, found := lhm.store[key]; lhm.Empty() || !found || len(items) == 0 {
panic(gtl.OutOfRange)
}

return lhm.store[key][idx]
}
197 changes: 197 additions & 0 deletions containers/linked_hash_map/linked_hash_map_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
// Copyright 2021. The GTL Authors. All rights reserved.
// https://github.com/modern-dev/gtl
// Use of this source code is governed by the MIT
// license that can be found in the LICENSE file.

package linked_hash_map

import (
"math/rand"
"strconv"
"testing"
)

func TestLinkedHashMapCapacityMembers(t *testing.T) {
lhm := NewLinkedHashMap[int, string](false)
lhmm := NewLinkedHashMap[int, string](true)

if !lhm.Empty() {
t.Errorf("expected lhm not to be empty")
}

if !lhmm.Empty() {
t.Errorf("expected lhmm not to be empty")
}

for _, num := range []int{1, 2, 3, 4, 5, 5} {
lhm.Insert(num, strconv.Itoa(num))
lhmm.Insert(num, strconv.Itoa(num))
}

if lhm.Size() != 5 {
t.Errorf("expected lhm to be the size of 5")
}

if lhmm.Size() != 6 {
t.Errorf("expected lhmm to be the size of 6")
}

lhm.Clear()
lhmm.Clear()

if !lhm.Empty() {
t.Errorf("expected lhm not to be empty")
}

if !lhmm.Empty() {
t.Errorf("expected lhmm not to be empty")
}
}

func TestLinkedHashMapModifierAndLookupMembers(t *testing.T) {
cases := []struct {
name string
insert []int
expected []int
}{
{"Example 1", []int{1, 2, 3, 4, 5}, []int{1, 3, 5}},
{"Example 2", []int{-10, -50, 10, 50}, []int{-10, 10}},
}

for _, tt := range cases {
t.Run(tt.name, func(t *testing.T) {
lhm := NewLinkedHashMap[int, string](false)

for _, num := range tt.insert {
lhm.Insert(num, strconv.Itoa(num))
}

for _, num := range tt.expected {
if !lhm.Contains(num) {
t.Errorf("expected container to have value {%d}, but got false", num)
}

if lhm.At(num) != strconv.Itoa(num) {
t.Errorf("expected key {%d} to have value {%s}", num, strconv.Itoa(num))
}
}
})
}
}

func TestLinkedHashMapModifierAndLookupMembers2(t *testing.T) {
cases := []struct {
name string
insert []int
removed []int
}{
{"Example 1", []int{1, 2, 3, 4, 5}, []int{1, 3, 5}},
{"Example 2", []int{-10, -50, 10, 50}, []int{-10, 10}},
}

for _, tt := range cases {
t.Run(tt.name, func(t *testing.T) {
lhm := NewLinkedHashMap[int, string](false)

for _, num := range tt.insert {
lhm.Insert(num, strconv.Itoa(num))
}

for _, num := range tt.removed {
lhm.Erase(num)
}

for _, num := range tt.removed {
if lhm.Contains(num) {
t.Errorf("expected container not to have value {%d}", num)
}
}
})
}
}

func TestLinkedHashMapModifierAndLookupMembers3(t *testing.T) {
cases := []struct {
name string
insert []int
modify [][]int
expected [][]int
}{
{
"Example 1",
[]int{1, 2, 3, 4, 5},
[][]int{{1, 10}, {3, 30}, {5, 50}, {7, 70}},
[][]int{{3, 30}, {7, 70}},
}, {
"Example 2",
[]int{-10, -50, 10, 50},
[][]int{{-10, -20}, {10, 20}, {100, 200}},
[][]int{{-10, -20}, {100, 200}}},
}

for _, tt := range cases {
t.Run(tt.name, func(t *testing.T) {
lhm := NewLinkedHashMap[int, int](false)

for _, num := range tt.insert {
lhm.Insert(num, num)
}

for _, mod := range tt.modify {
lhm.InsertOrAssign(mod[0], mod[1])
}

for _, exp := range tt.expected {
if !lhm.Contains(exp[0]) {
t.Errorf("expected container to have the key of {%d}", exp[0])
}

if lhm.At(exp[0]) != exp[1] {
t.Errorf("expected container to have the key of {%d} be associated with {%d}, got {%d} instead",
exp[0], exp[1], lhm.At(exp[0]))
}
}
})
}
}

func TestLinkedHashMultiMapModifierAndLookupMembers(t *testing.T) {
cases := []struct {
name string
insert []int
expected [][]int
}{
{"Example 1", []int{1, 2, 3, 4, 5, 3, 5, 5}, [][]int{{1, 1}, {3, 2}, {5, 3}, {7, 0}}},
{"Example 2", []int{-10, -50, 10, 50, 10, 10, 1}, [][]int{{10, 3}, {1, 1}, {-20, 0}}},
}

for _, tt := range cases {
t.Run(tt.name, func(t *testing.T) {
lhm, rndInt := NewLinkedHashMap[int, string](true), rand.Intn(100)

for _, num := range tt.insert {
lhm.Insert(num, strconv.Itoa(num))
}

for _, exp := range tt.expected {
if lhm.Count(exp[0]) != exp[1] {
t.Errorf("expected container to have {%d} key {%d} times, got {%d} instead",
exp[0], exp[1], lhm.Count(exp[0]))
}
}

lhm.Clear()

if !lhm.Empty() {
t.Errorf("expected container to be empty")
}

defer func() { recover() }()

lhm.Erase(rndInt)
lhm.At(rndInt)

t.Errorf("expected to panic")
})
}
}
Loading