Skip to content

Commit

Permalink
Dev linked hash table head api (#33)
Browse files Browse the repository at this point in the history
* add LinkedSet interface and impl methods for linkedHashTable

* add Set.Replace in addition to Add

* add interface LinkedMap with methods impl
  • Loading branch information
Cause Chung authored Dec 7, 2022
1 parent c2e6e39 commit 880ff2d
Show file tree
Hide file tree
Showing 15 changed files with 308 additions and 45 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ All interface definitions can be found: [here](./types/collection.go)
* `binaryHeap` - binary heap based min or max priority queue. Backing up `Queue`
* `rbTree` - recursion-free red black tree implementation. Backing up `SortedSet`, `SortedMap`
* `hashTable` - variable length/cap array based hash table, hash collision is handled by linked nodes. Backing up `Set`, `Map`
* `linkedHashTable` hashTable preserving inserting or configurable access order. Can serve as an `LRU cache`. Backing up `Set`, `Map`
* `linkedHashTable` hashTable preserving inserting or configurable access order. Can serve as an `LRU cache`. Backing up `LinkedSet`, `LinkedMap`
* `enumMap` & `enumSet` fast array based map and set with `Integer` as the key. Implementing `SortedMap`, `SortedSet` respectively.
* `multiMap` multimap implementation with `arrayListMultiMap` and `hashSetMultiMap` constructors.
* `treeAdjacencyList` a treeMap based graph implementation with directional edge properties.
Expand Down
4 changes: 2 additions & 2 deletions collections/defs.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,6 @@ type AccessOrder byte

const (
OriginalOrder AccessOrder = 0 // original put order (no explicit order change)
PutOrder AccessOrder = 1 // newly put element will be at the tail, the elem at head will be next for eviction
GetOrder AccessOrder = 2 // newly get element will be at the tail, the elem at head will be next for eviction
PutOrder AccessOrder = 1 // newly put element will be at the tail (the elem at head will be next for eviction).
GetOrder AccessOrder = 2 // newly get element will be at the tail (the elem at head will be next for eviction).
)
9 changes: 7 additions & 2 deletions collections/enum_set.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,18 @@ func newEnumSet[T constraints.Integer](max T, values ...T) *enumSet[T] {
}

func (s *enumSet[T]) Add(elem T) bool {
_, existing := s.Replace(elem)
return !existing
}

func (s *enumSet[T]) Replace(elem T) (T, bool) {
e := s.arr[elem]
if e == nil {
s.size++
s.arr[elem] = keyEntry[T, interface{}]{elem}
return true
return utils.Nil[T](), false
}
return false
return e.Key(), true
}

func (s *enumSet[T]) Contains(elem T) bool {
Expand Down
10 changes: 8 additions & 2 deletions collections/hash_table.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,16 @@ func (h *hashTable[T]) Add(elem T) bool {
return true
}

func (h *hashTable[T]) Replace(elem T) (T, bool) {
_, old, found := h.add(elem)
return old, found
}

// add inserts the elem and return:
//
// n - the node containing the elem
// old - existing elem if found
// n - the node containing the elem;
// old - existing elem if found;
// found - if found an existing elem.
func (h *hashTable[T]) add(elem T) (n node[T], old T, found bool) {
h.expandIfNeeded()
i := hashToIndex(h.hs(elem), cap(h.arr))
Expand Down
7 changes: 5 additions & 2 deletions collections/hash_table_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,11 @@ func TestHashTable(t *testing.T) {
h := newHashTable[int](funcs.NumHash[int], funcs.ValueEqual[int])
assert.False(t, h.Contains(7))
h.Add(1)
assert.False(t, h.Contains(7))
h.Add(2)
v, found := h.Replace(2)
assert.False(t, found)
v, found = h.Replace(2)
assert.True(t, found)
assert.Equal(t, 2, v)
h.Add(3)
h.Add(4)
assert.True(t, h.Add(5))
Expand Down
4 changes: 2 additions & 2 deletions collections/iterators.go
Original file line number Diff line number Diff line change
Expand Up @@ -386,8 +386,8 @@ func (l *linkedList[T]) Each(fn func(index int, elem T)) {
forEach[T](l, fn)
}

func (s *rbTree[T]) Each(fn func(index int, elem T)) {
forEach[T](s, fn)
func (t *rbTree[T]) Each(fn func(index int, elem T)) {
forEach[T](t, fn)
}

func (h *hashTable[T]) Each(fn func(index int, elem T)) {
Expand Down
48 changes: 48 additions & 0 deletions collections/linked_hash_map.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,51 @@ func (h *linkedHashMap[K, V]) Remove(k K) (V, bool) {
func (h *linkedHashMap[K, V]) ContainsKey(k K) bool {
return h.linkedHashTable.Contains(keyEntry[K, V]{k})
}

func (h *linkedHashMap[K, V]) PutHead(k K, v V) (V, bool) {
e, found := h.linkedHashTable.AddHead(EntryOf[K, V](k, v))
if found {
return e.Value(), true
}
return utils.Nil[V](), false
}

func (h *linkedHashMap[K, V]) RemoveHead() (K, V, bool) {
e, found := h.linkedHashTable.RemoveHead()
if found {
return e.Key(), e.Value(), true
}
return utils.Nil[K](), utils.Nil[V](), false
}

func (h *linkedHashMap[K, V]) Head() (K, V, bool) {
e, found := h.linkedHashTable.Head()
if found {
return e.Key(), e.Value(), true
}
return utils.Nil[K](), utils.Nil[V](), false
}

func (h *linkedHashMap[K, V]) PutTail(k K, v V) (V, bool) {
e, found := h.linkedHashTable.AddTail(EntryOf(k, v))
if found {
return e.Value(), true
}
return utils.Nil[V](), false
}

func (h *linkedHashMap[K, V]) RemoveTail() (K, V, bool) {
e, found := h.linkedHashTable.RemoveTail()
if found {
return e.Key(), e.Value(), true
}
return utils.Nil[K](), utils.Nil[V](), false
}

func (h *linkedHashMap[K, V]) Tail() (K, V, bool) {
e, found := h.linkedHashTable.Tail()
if found {
return e.Key(), e.Value(), true
}
return utils.Nil[K](), utils.Nil[V](), false
}
53 changes: 53 additions & 0 deletions collections/linked_hash_map_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,56 @@ func TestLinkedHashMap_AccessOrder(t *testing.T) {
assert.Equal(t, []int{3, 2, 1}, l)
})
}

func TestLinkedHashMap_Head_Tail(t *testing.T) {
m := newLinkedHashMap[int, int](funcs.NumHash[int], funcs.ValueEqual[int], 0, OriginalOrder)
k, v, found := m.Head()
assert.False(t, found)
k, v, found = m.Tail()
assert.False(t, found)
k, v, found = m.RemoveHead()
assert.False(t, found)
k, v, found = m.RemoveTail()
assert.False(t, found)

v, found = m.PutHead(3, 33)
assert.False(t, found)
v, found = m.PutHead(3, 333)
assert.True(t, found)
assert.Equal(t, 33, v)

v, found = m.PutTail(4, 44)
assert.False(t, found)
v, found = m.PutTail(4, 444)
assert.True(t, found)
assert.Equal(t, 44, v)

v, found = m.PutTail(5, 55)
v, found = m.PutHead(2, 22)

k, v, found = m.Head()
assert.True(t, found)
assert.Equal(t, 2, k)
assert.Equal(t, 22, v)
k, v, found = m.Tail()
assert.True(t, found)
assert.Equal(t, 5, k)
assert.Equal(t, 55, v)
assert.Equal(t, 4, m.Size())

k, v, found = m.RemoveHead()
assert.True(t, found)
assert.Equal(t, 2, k)
assert.Equal(t, 22, v)
k, v, found = m.RemoveTail()
assert.True(t, found)
assert.Equal(t, 5, k)
assert.Equal(t, 55, v)
assert.Equal(t, 2, m.Size())

m.Clear()
m.PutTail(1, 11)
k, v, found = m.RemoveHead()
assert.Equal(t, 1, k)
assert.Equal(t, 11, v)
}
107 changes: 91 additions & 16 deletions collections/linked_hash_table.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package collections

import (
"github.com/cuzfrog/tgods/types"
"github.com/cuzfrog/tgods/utils"
)

type linkedHashTable[T any] struct {
Expand All @@ -20,30 +21,41 @@ func (h *linkedHashTable[T]) Add(elem T) bool {
return true
}

func (h *linkedHashTable[T]) Replace(elem T) (T, bool) {
_, old, found := h.add(elem)
return old, found
}

// add returns the newly added or existing node
func (h *linkedHashTable[T]) add(elem T) (n node[T], old T, found bool) {
if h.tail == nil {
h.tail = newDlNode(elem, nil, nil)
h.head = h.tail
x := h.tail
n, old, found = h.hashTable.add(elem)
n.SetExternal(x)
} else {
n, old, found = h.hashTable.add(elem)
if found {
if h.accessOrder&PutOrder > 0 {
x := n.External()
h.removeNode(x)
h.appendToTail(x)
}
} else {
x := newDlNode(elem, h.tail, nil)
n.SetExternal(x)
return h.addFirstNode(elem)
}
n, old, found = h.hashTable.add(elem)
if found {
if h.accessOrder&PutOrder > 0 {
x := n.External()
h.removeNode(x)
h.appendToTail(x)
}
} else {
x := newDlNode(elem, h.tail, nil)
n.SetExternal(x)
h.appendToTail(x)
}
return
}

// should only be called when size == 0
func (h *linkedHashTable[T]) addFirstNode(elem T) (n node[T], old T, found bool) {
h.tail = newDlNode(elem, nil, nil)
h.head = h.tail
x := h.tail
n, old, found = h.hashTable.add(elem)
n.SetExternal(x)
return
}

func (h *linkedHashTable[T]) appendToTail(x node[T]) {
if h.tail == nil {
h.tail = x
Expand All @@ -55,6 +67,17 @@ func (h *linkedHashTable[T]) appendToTail(x node[T]) {
}
}

func (h *linkedHashTable[T]) prependToHead(x node[T]) {
if h.head == nil {
h.tail = x
h.head = x
} else {
x.SetNext(h.head)
h.head.SetPrev(x)
h.head = x
}
}

func (h *linkedHashTable[T]) Remove(elem T) bool {
n := h.remove(elem)
return n != nil
Expand Down Expand Up @@ -85,3 +108,55 @@ func (h *linkedHashTable[T]) Clear() {
h.head = nil
h.tail = nil
}

func (h *linkedHashTable[T]) AddHead(elem T) (T, bool) {
n, old, found := h.hashTable.add(elem)
if found {
x := n.External()
h.removeNode(x)
h.prependToHead(x)
} else {
x := newDlNode(elem, nil, h.head)
n.SetExternal(x)
h.prependToHead(x)
}
return old, found
}

func (h *linkedHashTable[T]) RemoveHead() (T, bool) {
if h.head == nil {
return utils.Nil[T](), false
}
elem := h.head.Value()
h.remove(elem)
return elem, true
}

func (h *linkedHashTable[T]) Head() (T, bool) {
if h.head == nil {
return utils.Nil[T](), false
}
return h.head.Value(), true
}

func (h *linkedHashTable[T]) RemoveTail() (T, bool) {
if h.tail == nil {
return utils.Nil[T](), false
}
elem := h.tail.Value()
h.remove(elem)
return elem, true
}

// AddTail equivalent to Add with different return types
func (h *linkedHashTable[T]) AddTail(elem T) (T, bool) {
_, old, found := h.add(elem)
return old, found
}

func (h *linkedHashTable[T]) Tail() (T, bool) {
if h.tail == nil {
return utils.Nil[T](), false
}
return h.tail.Value(), true
}
47 changes: 45 additions & 2 deletions collections/linked_hash_table_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,16 @@ import (
func TestLinkedHashTable_Add(t *testing.T) {
h := newLinkedHashTable[int](funcs.NumHash[int], funcs.ValueEqual[int], 1)
h.Add(2)
h.Add(6)
h.Add(3)
old, found := h.AddTail(6)
assert.False(t, found)
old, found = h.AddTail(6)
assert.Equal(t, 6, old)
assert.True(t, found)
old, found = h.Replace(3)
assert.False(t, found)
old, found = h.Replace(3)
assert.True(t, found)
assert.Equal(t, 3, old)
assert.Equal(t, 2, h.head.Value())
assert.Equal(t, 6, h.head.Next().Value())
assert.Equal(t, 3, h.head.Next().Next().Value())
Expand Down Expand Up @@ -65,3 +73,38 @@ func TestLinkedHashTable_Remove(t *testing.T) {
assert.Nil(t, h.tail)
assert.Equal(t, 0, h.size)
}

func TestLinkedHashTable_Head_Tail(t *testing.T) {
h := newLinkedHashTable[int](funcs.NumHash[int], funcs.ValueEqual[int], OriginalOrder)
v, found := h.Head()
assert.False(t, found)
v, found = h.Tail()
assert.False(t, found)
v, found = h.RemoveHead()
assert.False(t, found)
v, found = h.RemoveTail()
assert.False(t, found)

h.AddHead(3)
v, found = h.AddHead(2)
assert.False(t, found)
h.Add(4)
v, found = h.AddHead(2)
assert.True(t, found)
assert.Equal(t, 2, v)
assert.Equal(t, []int{2, 3, 4}, utils.SliceFrom[int](h))
v, found = h.Head()
assert.True(t, found)
assert.Equal(t, 2, v)
v, found = h.RemoveHead()
assert.Equal(t, 2, v)
assert.Equal(t, []int{3, 4}, utils.SliceFrom[int](h))

v, found = h.Tail()
assert.Equal(t, 4, v)
h.AddTail(5)
assert.Equal(t, []int{3, 4, 5}, utils.SliceFrom[int](h))
v, found = h.RemoveTail()
assert.Equal(t, 5, v)
assert.Equal(t, []int{3, 4}, utils.SliceFrom[int](h))
}
Loading

0 comments on commit 880ff2d

Please sign in to comment.