Compare commits

..
7 Commits
15 changed files with 582 additions and 71 deletions

No files matched your search

-1
View File
@@ -6,7 +6,6 @@
![Go version](https://img.shields.io/badge/go-%3E%3Dv1.18-9cf)
[![Release](https://img.shields.io/badge/release-2.4.0-green.svg)](https://github.com/duke-git/lancet/releases)
[![GoDoc](https://godoc.org/github.com/duke-git/lancet/v2?status.svg)](https://pkg.go.dev/github.com/duke-git/lancet/v2)
[![Go Report Card](https://goreportcard.com/badge/github.com/duke-git/lancet/v2)](https://goreportcard.com/report/github.com/duke-git/lancet/v2)
[![test](https://github.com/duke-git/lancet/actions/workflows/codecov.yml/badge.svg?branch=main&event=push)](https://github.com/duke-git/lancet/actions/workflows/codecov.yml)
[![codecov](https://codecov.io/gh/duke-git/lancet/branch/main/graph/badge.svg?token=FC48T1F078)](https://codecov.io/gh/duke-git/lancet)
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/duke-git/lancet/blob/main/LICENSE)
-1
View File
@@ -6,7 +6,6 @@
![Go version](https://img.shields.io/badge/go-%3E%3Dv1.18-9cf)
[![Release](https://img.shields.io/badge/release-2.4.0-green.svg)](https://github.com/duke-git/lancet/releases)
[![GoDoc](https://godoc.org/github.com/duke-git/lancet/v2?status.svg)](https://pkg.go.dev/github.com/duke-git/lancet/v2)
[![Go Report Card](https://goreportcard.com/badge/github.com/duke-git/lancet/v2)](https://goreportcard.com/report/github.com/duke-git/lancet/v2)
[![test](https://github.com/duke-git/lancet/actions/workflows/codecov.yml/badge.svg?branch=main&event=push)](https://github.com/duke-git/lancet/actions/workflows/codecov.yml)
[![codecov](https://codecov.io/gh/duke-git/lancet/branch/main/graph/badge.svg?token=FC48T1F078)](https://codecov.io/gh/duke-git/lancet)
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/duke-git/lancet/blob/main/LICENSE)
+76
View File
@@ -0,0 +1,76 @@
package algorithm
import (
"strconv"
"testing"
)
func TestLRUCacheReuseAfterDeletingLastEntry(t *testing.T) {
cache := NewLRUCache[int, string](1)
cache.Put(1, "one")
if !cache.Delete(1) || cache.Len() != 0 {
t.Fatal("deleting the sole entry must empty the cache")
}
cache.Put(2, "two")
cache.Put(3, "three")
if cache.Len() != 1 {
t.Fatalf("Len() = %d, want capacity 1", cache.Len())
}
if _, ok := cache.Get(2); ok {
t.Fatal("the least recently used replacement entry was not evicted")
}
if value, ok := cache.Get(3); !ok || value != "three" {
t.Fatalf("Get(3) = %q, %v", value, ok)
}
}
func TestLRUCacheEmptyAndReuse(t *testing.T) {
for _, capacity := range []int{0, 1, 2, 4} {
t.Run(strconv.Itoa(capacity), func(t *testing.T) {
defer func() {
if value := recover(); value != nil {
t.Errorf("cache reuse panicked: %v", value)
}
}()
cache := NewLRUCache[int, int](capacity)
for round := 0; round < 4; round++ {
for i := 0; i < capacity+2; i++ {
cache.Put(i, round*10+i)
if cache.Len() > capacity {
t.Fatalf("Len() = %d exceeds capacity %d", cache.Len(), capacity)
}
}
for i := 0; i < capacity+2; i++ {
cache.Delete(i)
}
if cache.Len() != 0 {
t.Fatalf("Len() = %d after deleting every key", cache.Len())
}
}
})
}
}
func TestLRUCacheEndpointDeletionKeepsRecency(t *testing.T) {
cache := NewLRUCache[int, int](3)
cache.Put(1, 10)
cache.Put(2, 20)
cache.Put(3, 30)
cache.Delete(1)
cache.Delete(3)
cache.Put(4, 40)
cache.Put(5, 50)
cache.Get(2)
cache.Put(6, 60)
if cache.Len() != 3 {
t.Fatalf("Len() = %d, want 3", cache.Len())
}
if _, ok := cache.Get(4); ok {
t.Fatal("Get(2) should make 4 the least recently used entry")
}
for key, want := range map[int]int{2: 20, 5: 50, 6: 60} {
if got, ok := cache.Get(key); !ok || got != want {
t.Fatalf("Get(%d) = %d, %v; want %d", key, got, ok, want)
}
}
}
+6 -4
View File
@@ -102,12 +102,14 @@ func (l *LRUCache[K, V]) addNode(node *lruNode[K, V]) {
}
func (l *LRUCache[K, V]) deleteNode(node *lruNode[K, V]) K {
if node == l.tail {
l.tail = l.tail.pre
} else if node == l.head {
l.head = l.head.next
if node.pre == nil {
l.head = node.next
} else {
node.pre.next = node.next
}
if node.next == nil {
l.tail = node.pre
} else {
node.next.pre = node.pre
}
return node.key
@@ -0,0 +1,138 @@
package datastructure_test
import (
"fmt"
"reflect"
"testing"
hashmap "github.com/duke-git/lancet/v2/datastructure/hashmap"
)
type collisionKey string
func TestHashMapCollisionStorage(t *testing.T) {
cases := map[string]func(*testing.T){
"distinct colliding keys": func(t *testing.T) {
h := hashmap.NewHashMapWithCapacity(0, 1)
keys := []any{1, "1", collisionKey("1")}
for i, k := range keys {
h.Put(k, i+10)
}
for i, k := range keys {
if h.Get(k) != i+10 || !h.Contains(k) {
t.Fatalf("lost key %T(%v): %v", k, k, h.Get(k))
}
}
if h.Size() != 3 {
t.Fatalf("size=%d", h.Size())
}
},
"overwrite cardinality": func(t *testing.T) {
h := hashmap.NewHashMap()
h.Put("same", 1)
h.Put("same", 2)
if h.Size() != 1 || !reflect.DeepEqual(h.Keys(), []any{"same"}) || !reflect.DeepEqual(h.Values(), []any{2}) {
t.Fatalf("overwrite size/keys/values: %d %v %v", h.Size(), h.Keys(), h.Values())
}
},
"delete absent colliding key": func(t *testing.T) {
h := hashmap.NewHashMap()
h.Put(1, "kept")
h.Delete("1")
if h.Size() != 1 || h.Get(1) != "kept" {
t.Fatalf("absent delete removed another key: size=%d get=%v", h.Size(), h.Get(1))
}
},
"delete every chain position": func(t *testing.T) {
for _, remove := range []any{1, "1", collisionKey("1")} {
h := hashmap.NewHashMapWithCapacity(0, 4)
keys := []any{1, "1", collisionKey("1")}
for i, k := range keys {
h.Put(k, i)
}
h.Delete(remove)
if h.Contains(remove) || h.Size() != 2 {
t.Fatalf("deletion failed: %T size=%d", remove, h.Size())
}
for i, k := range keys {
if !reflect.DeepEqual(k, remove) && h.Get(k) != i {
t.Fatalf("delete lost other key: %T", k)
}
}
}
},
"delete preserves bucket indexes": func(t *testing.T) {
h := hashmap.NewHashMap()
for i := 0; i < 100; i++ {
h.Put(fmt.Sprintf("key-%d", i), i)
}
for i := 0; i < 100; i += 2 {
h.Delete(fmt.Sprintf("key-%d", i))
}
for i := 1; i < 100; i += 2 {
if h.Get(fmt.Sprintf("key-%d", i)) != i {
t.Fatalf("lost untouched bucket key %d", i)
}
}
if h.Size() != 50 {
t.Fatalf("size=%d", h.Size())
}
},
"resize and enumerate chains": func(t *testing.T) {
h := hashmap.NewHashMapWithCapacity(0, 1)
h.Put(1, 1)
h.Put("1", 2)
h.Put(collisionKey("1"), 3)
for i := 2; i <= 64; i++ {
h.Put(i, i)
}
seen := map[string]any{}
h.Iterate(func(k, v any) { seen[fmt.Sprintf("%T:%v", k, k)] = v })
if h.Size() != 66 || len(seen) != 66 || len(h.Keys()) != 66 || len(h.Values()) != 66 {
t.Fatalf("size=%d enumerated=%d", h.Size(), len(seen))
}
if seen["string:1"] != 2 || h.Get(collisionKey("1")) != 3 {
t.Fatal("collision chain lost during growth")
}
},
"filter colliding values": func(t *testing.T) {
h := hashmap.NewHashMapWithCapacity(0, 4)
h.Put(1, 1)
h.Put("1", 2)
h.Put(collisionKey("1"), 3)
f := h.FilterByValue(func(v any) bool { return v.(int)%2 == 1 })
if f == nil || f.Size() != 2 || f.Get(1) != 1 || f.Get(collisionKey("1")) != 3 || f.Contains("1") {
t.Fatal("filter skipped or corrupted collision chain")
}
if h.FilterByValue(func(any) bool { return false }) != nil {
t.Fatal("empty-filter nil contract changed")
}
},
"zero capacity and deep equal keys": func(t *testing.T) {
h := hashmap.NewHashMapWithCapacity(0, 0)
if h.Get("missing") != nil || h.Contains("missing") {
t.Fatal("empty lookup")
}
h.Delete("missing")
h.Put([]int{1, 2}, "first")
h.Put([]int{1, 2}, "second")
if h.Get([]int{1, 2}) != "second" || h.Size() != 1 {
t.Fatal("deep equal overwrite failed")
}
h.Delete([]int{1, 2})
if h.Size() != 0 {
t.Fatal("deep equal delete failed")
}
},
}
for name, fn := range cases {
t.Run(name, func(t *testing.T) {
defer func() {
if r := recover(); r != nil {
t.Fatalf("public map operation panicked: %v", r)
}
}()
fn(t)
})
}
}
+52 -49
View File
@@ -44,6 +44,9 @@ func NewHashMapWithCapacity(size, capacity uint64) *HashMap {
// Get return the value of given key in hashmap
func (hm *HashMap) Get(key any) any {
if hm.size == 0 {
return nil
}
hashValue := hm.hash(key)
node := hm.table[hashValue]
for node != nil {
@@ -66,41 +69,55 @@ func (hm *HashMap) GetOrDefault(key any, defaultValue any) any {
// Put new key value in hashmap
func (hm *HashMap) Put(key any, value any) {
hm.putValue(hm.hash(key), key, value)
}
func (hm *HashMap) putValue(hash uint64, key, value any) {
if hm.capacity == 0 {
hm.capacity = defaultMapCapacity
hm.table = make([]*mapNode, defaultMapCapacity)
}
hm.putValue(hm.hash(key), key, value)
}
node := hm.table[hash]
if node == nil {
hm.table[hash] = newMapNode(key, value)
} else if node.key == key {
hm.table[hash] = newMapNodeWithNext(key, value, node)
} else {
hm.resize()
hm.putValue(hash, value, value)
func (hm *HashMap) putValue(hash uint64, key, value any) {
for node := hm.table[hash]; node != nil; node = node.next {
if reflect.DeepEqual(node.key, key) {
node.value = value
return
}
}
if hm.size >= hm.capacity {
hm.resize()
hash = hm.hash(key)
}
hm.table[hash] = newMapNodeWithNext(key, value, hm.table[hash])
hm.size++
}
// Delete item by given key in hashmap
func (hm *HashMap) Delete(key any) {
hash := hm.hash(key)
node := hm.table[hash]
if node == nil {
if hm.size == 0 {
return
}
hm.table = append(hm.table[:hash], hm.table[hash+1:]...)
hm.size--
hash := hm.hash(key)
var previous *mapNode
for node := hm.table[hash]; node != nil; node = node.next {
if reflect.DeepEqual(node.key, key) {
if previous == nil {
hm.table[hash] = node.next
} else {
previous.next = node.next
}
hm.size--
return
}
previous = node
}
}
// Contains checks if given key is in hashmap or not
func (hm *HashMap) Contains(key any) bool {
if hm.size == 0 {
return false
}
node := hm.table[hm.hash(key)]
for node != nil {
if reflect.DeepEqual(node.key, key) {
@@ -113,12 +130,10 @@ func (hm *HashMap) Contains(key any) bool {
// Iterate executes iteratee funcation for every key and value pair of hashmap (random order)
func (hm *HashMap) Iterate(iteratee func(key, value any)) {
if hm.size > 0 {
for i := 0; i < len(hm.table); i++ {
item := hm.table[i]
if item != nil {
iteratee(item.key, item.value)
}
for _, node := range hm.table {
for node != nil {
iteratee(node.key, node.value)
node = node.next
}
}
}
@@ -128,17 +143,14 @@ func (hm *HashMap) Iterate(iteratee func(key, value any)) {
// otherwise it returns the HashMap with selected values.
func (hm *HashMap) FilterByValue(perdicate func(value any) bool) *HashMap {
var filteredHM *HashMap
if hm.size > 0 {
for i := 0; i < len(hm.table); i++ {
item := hm.table[i]
if item != nil && perdicate(item.value) {
if filteredHM == nil {
filteredHM = NewHashMap()
}
filteredHM.Put(item.key, item.value)
hm.Iterate(func(key, value any) {
if perdicate(value) {
if filteredHM == nil {
filteredHM = NewHashMap()
}
filteredHM.Put(key, value)
}
}
})
return filteredHM
}
@@ -172,18 +184,16 @@ func (hm *HashMap) Values() []any {
func (hm *HashMap) resize() {
hm.capacity <<= 1
tempTable := hm.table
hm.table = make([]*mapNode, hm.capacity)
for i := 0; i < len(tempTable); i++ {
node := tempTable[i]
if node == nil {
continue
for _, node := range tempTable {
for node != nil {
next := node.next
hash := hm.hash(node.key)
node.next = hm.table[hash]
hm.table[hash] = node
node = next
}
hm.table[hm.hash(node.key)] = node
}
}
@@ -201,13 +211,6 @@ func (hm *HashMap) hash(key any) uint64 {
return (hm.capacity - 1) & (hashValue ^ (hashValue >> 16))
}
func newMapNode(key, value any) *mapNode {
return &mapNode{
key: key,
value: value,
}
}
func newMapNodeWithNext(key, value any, next *mapNode) *mapNode {
return &mapNode{
key: key,
+10 -7
View File
@@ -87,6 +87,7 @@ func (dl *DoublyLink[T]) InsertAt(index int, value T) {
newNode := datastructure.NewLinkNode(value)
newNode.Next = current.Next
newNode.Pre = current
current.Next.Pre = newNode
current.Next = newNode
dl.length++
@@ -106,7 +107,9 @@ func (dl *DoublyLink[T]) DeleteAtHead() {
current := dl.Head
dl.Head = current.Next
dl.Head.Pre = nil
if dl.Head != nil {
dl.Head.Pre = nil
}
dl.length--
}
@@ -119,6 +122,7 @@ func (dl *DoublyLink[T]) DeleteAtTail() {
current := dl.Head
if current.Next == nil {
dl.DeleteAtHead()
return
}
for current.Next.Next != nil {
@@ -132,27 +136,26 @@ func (dl *DoublyLink[T]) DeleteAtTail() {
// DeleteAt delete value in doubly linklist at index
// param `index` should be [0, len(DoublyLink)-1]
func (dl *DoublyLink[T]) DeleteAt(index int) {
if dl.Head == nil {
if dl.Head == nil || index < 0 || index >= dl.length {
return
}
current := dl.Head
if current.Next == nil || index == 0 {
if index == 0 {
dl.DeleteAtHead()
return
}
if index == dl.length-1 {
dl.DeleteAtTail()
}
if index < 0 || index > dl.length-1 {
return
}
current := dl.Head
i := 0
for current != nil {
if i == index-1 {
current.Next = current.Next.Next
current.Next.Pre = current
dl.length--
return
}
@@ -0,0 +1,115 @@
package datastructure
import (
"reflect"
"testing"
)
type mutableIntLink interface {
InsertAtHead(int)
InsertAtTail(int)
DeleteAtHead()
DeleteAtTail()
DeleteAt(int)
Reverse()
Values() []int
Size() int
IsEmpty() bool
}
func TestLinkDeletionBoundaries(t *testing.T) {
for name, newLink := range map[string]func() mutableIntLink{
"singly": func() mutableIntLink { return NewSinglyLink[int]() },
"doubly": func() mutableIntLink { return NewDoublyLink[int]() },
} {
t.Run(name, func(t *testing.T) {
for _, tt := range []struct {
name string
size int
delete func(mutableIntLink)
want []int
}{
{"empty head", 0, func(l mutableIntLink) { l.DeleteAtHead() }, []int{}},
{"empty tail", 0, func(l mutableIntLink) { l.DeleteAtTail() }, []int{}},
{"empty index", 0, func(l mutableIntLink) { l.DeleteAt(0) }, []int{}},
{"singleton head", 1, func(l mutableIntLink) { l.DeleteAtHead() }, []int{}},
{"singleton tail", 1, func(l mutableIntLink) { l.DeleteAtTail() }, []int{}},
{"singleton index", 1, func(l mutableIntLink) { l.DeleteAt(0) }, []int{}},
{"singleton negative index", 1, func(l mutableIntLink) { l.DeleteAt(-1) }, []int{1}},
{"singleton large index", 1, func(l mutableIntLink) { l.DeleteAt(1) }, []int{1}},
{"two item head index", 2, func(l mutableIntLink) { l.DeleteAt(0) }, []int{2}},
{"two item tail index", 2, func(l mutableIntLink) { l.DeleteAt(1) }, []int{1}},
{"middle index", 4, func(l mutableIntLink) { l.DeleteAt(1) }, []int{1, 3, 4}},
{"tail index", 4, func(l mutableIntLink) { l.DeleteAt(3) }, []int{1, 2, 3}},
{"negative index", 4, func(l mutableIntLink) { l.DeleteAt(-1) }, []int{1, 2, 3, 4}},
{"large index", 4, func(l mutableIntLink) { l.DeleteAt(4) }, []int{1, 2, 3, 4}},
} {
t.Run(tt.name, func(t *testing.T) {
defer func() {
if value := recover(); value != nil {
t.Errorf("list mutation panicked: %v", value)
}
}()
l := newLink()
for i := 1; i <= tt.size; i++ {
l.InsertAtTail(i)
}
tt.delete(l)
if got := l.Values(); !reflect.DeepEqual(got, tt.want) {
t.Fatalf("Values() = %v, want %v", got, tt.want)
}
if l.Size() != len(tt.want) || l.IsEmpty() != (len(tt.want) == 0) {
t.Fatalf("Size() = %d, IsEmpty() = %v, want length %d", l.Size(), l.IsEmpty(), len(tt.want))
}
if doubly, ok := l.(*DoublyLink[int]); ok {
current := doubly.Head
for i := 0; current != nil; i++ {
if i >= l.Size() {
t.Fatal("cycle in list after deletion")
}
if current.Next != nil && current.Next.Pre != current {
t.Fatal("deletion did not preserve reciprocal links")
}
current = current.Next
}
}
l.Reverse()
wantReverse := make([]int, len(tt.want))
for i, value := range tt.want {
wantReverse[len(tt.want)-1-i] = value
}
if got := l.Values(); !reflect.DeepEqual(got, wantReverse) {
t.Fatalf("Reverse Values() = %v, want %v", got, wantReverse)
}
l.Reverse()
l.InsertAtHead(9)
if got := l.Values(); !reflect.DeepEqual(got, append([]int{9}, tt.want...)) {
t.Fatalf("reused list Values() = %v", got)
}
})
}
})
}
}
func TestDoublyLinkMiddleMutationBacklinks(t *testing.T) {
l := NewDoublyLink[int]()
l.InsertAtTail(1)
l.InsertAtTail(3)
l.InsertAt(1, 2)
if l.Head.Next.Next.Pre != l.Head.Next {
t.Fatal("middle insertion did not update the next node's Pre link")
}
l.Reverse()
if got := l.Values(); !reflect.DeepEqual(got, []int{3, 2, 1}) {
t.Fatalf("Reverse() after insertion = %v", got)
}
l.DeleteAt(1)
if l.Head.Next.Pre != l.Head {
t.Fatal("middle deletion did not update the next node's Pre link")
}
l.Reverse()
if got := l.Values(); !reflect.DeepEqual(got, []int{1, 3}) {
t.Fatalf("Reverse() after deletion = %v", got)
}
}
+5 -6
View File
@@ -103,6 +103,7 @@ func (sl *SinglyLink[T]) DeleteAtTail() {
current := sl.Head
if current.Next == nil {
sl.DeleteAtHead()
return
}
for current.Next.Next != nil {
@@ -116,22 +117,20 @@ func (sl *SinglyLink[T]) DeleteAtTail() {
// DeleteAt delete value in singly linklist at index
// param `index` should be [0, len(SinglyLink)-1]
func (sl *SinglyLink[T]) DeleteAt(index int) {
if sl.Head == nil {
if sl.Head == nil || index < 0 || index >= sl.length {
return
}
current := sl.Head
if current.Next == nil || index == 0 {
if index == 0 {
sl.DeleteAtHead()
return
}
if index == sl.length-1 {
sl.DeleteAtTail()
}
if index < 0 || index > sl.length-1 {
return
}
current := sl.Head
i := 0
for current != nil {
if i == index-1 {
+1 -1
View File
@@ -354,6 +354,7 @@ func (c *CopyOnWriteList[T]) ForEach(f func(T)) {
func (c *CopyOnWriteList[T]) Sort(compare func(o1 T, o2 T) bool) {
lock := c.lock
lock.Lock()
defer lock.Unlock()
list := c.getList()
sort.Slice(list, func(i, j int) bool {
@@ -374,6 +375,5 @@ func (c *CopyOnWriteList[T]) SubList(start int, end int) (newList []T) {
}
newList = make([]T, end-start)
copy(newList, list[start:end])
c.setList(newList)
return
}
+69
View File
@@ -0,0 +1,69 @@
package datastructure_test
import (
"reflect"
"testing"
"time"
list "github.com/duke-git/lancet/v2/datastructure/list"
)
func TestCopyOnWriteSortReleasesLock(t *testing.T) {
for _, tc := range []struct {
name string
values []int
panicCompare bool
sort bool
}{
{"sorted values", []int{3, 1, 2}, false, true},
{"empty values", []int{}, false, true},
{"comparator panic", []int{3, 1, 2}, true, true},
{"without sort control", []int{1, 2, 3}, false, false},
} {
t.Run(tc.name, func(t *testing.T) {
c := list.NewCopyOnWriteList(tc.values)
marker := new(int)
if tc.sort {
func() {
defer func() {
r := recover()
if tc.panicCompare {
if r != marker {
t.Errorf("panic identity changed: %v", r)
}
} else if r != nil {
t.Errorf("unexpected panic: %v", r)
}
}()
c.Sort(func(a, b int) bool {
if tc.panicCompare {
panic(marker)
}
return a < b
})
}()
}
if tc.sort && !tc.panicCompare {
got := []int{}
c.ForEach(func(v int) { got = append(got, v) })
want := []int{1, 2, 3}
if len(tc.values) == 0 {
want = []int{}
}
if !reflect.DeepEqual(got, want) {
t.Fatalf("sort got=%v want=%v", got, want)
}
}
done := make(chan struct{})
go func() { c.Add(4); close(done) }()
select {
case <-done:
if c.Size() != len(tc.values)+1 {
t.Fatal("next mutation did not persist")
}
case <-time.After(time.Second):
t.Fatal("Sort left the mutation lock held")
}
})
}
}
@@ -0,0 +1,40 @@
package datastructure_test
import (
list "github.com/duke-git/lancet/v2/datastructure/list"
"reflect"
"testing"
)
func TestCopyOnWriteSubListPreservesReceiver(t *testing.T) {
for _, tc := range []struct {
name string
start, end int
want []int
}{
{"middle", 1, 3, []int{2, 3}}, {"empty range", 2, 2, []int{}}, {"prefix", 0, 2, []int{1, 2}}, {"suffix", 2, 4, []int{3, 4}}, {"full control", 0, 4, []int{1, 2, 3, 4}}, {"invalid control", 3, 1, []int{}},
} {
t.Run(tc.name, func(t *testing.T) {
c := list.NewCopyOnWriteList([]int{1, 2, 3, 4})
got := c.SubList(tc.start, tc.end)
if !reflect.DeepEqual(got, tc.want) {
t.Fatalf("sublist=%v want%v", got, tc.want)
}
values := []int{}
c.ForEach(func(v int) { values = append(values, v) })
if c.Size() != 4 || !reflect.DeepEqual(values, []int{1, 2, 3, 4}) {
t.Fatalf("SubList discarded source values: %v", values)
}
if len(got) > 0 {
got[0] = 99
}
if *c.Get(0) != 1 || *c.Get(1) != 2 {
t.Fatal("returned slice aliases receiver")
}
c.Add(5)
if c.Size() != 5 || *c.Get(4) != 5 {
t.Fatal("later append changed")
}
})
}
}
+1 -1
View File
@@ -40,7 +40,7 @@ func (t *BSTree[T]) Insert(data T) {
// DeletetNode delete data into BSTree
func (t *BSTree[T]) Delete(data T) {
deleteTreeNode(t.root, data, t.comparator)
t.root = deleteTreeNode(t.root, data, t.comparator)
}
// NodeLevel get node level in BSTree
+66
View File
@@ -0,0 +1,66 @@
package datastructure
import (
"sort"
"strconv"
"testing"
)
func TestBSTreeDeleteReplacesRoot(t *testing.T) {
for i, tt := range []struct {
values []int
remove int
want []int
}{
{nil, 4, nil},
{[]int{2, 1, 3}, 4, []int{1, 2, 3}},
{[]int{6, 5, 7}, 4, []int{5, 6, 7}},
{[]int{2, 6, 1, 3, 5, 7}, 4, []int{1, 2, 3, 5, 6, 7}},
{[]int{2, 6}, 9, []int{2, 4, 6}},
{[]int{2, 6}, 2, []int{4, 6}},
{[]int{2, 6, 1}, 2, []int{1, 4, 6}},
{[]int{2, 6, 1, 3}, 2, []int{1, 3, 4, 6}},
} {
t.Run(strconv.Itoa(i), func(t *testing.T) {
tree := NewBSTree(4, &intComparator{})
for _, value := range tt.values {
tree.Insert(value)
}
tree.Delete(tt.remove)
checkBSTValues(t, tree, tt.want)
tree.Delete(tt.remove)
checkBSTValues(t, tree, tt.want)
tree.Insert(8)
want := append(append([]int(nil), tt.want...), 8)
sort.Ints(want)
checkBSTValues(t, tree, want)
})
}
}
func checkBSTValues(t *testing.T, tree *BSTree[int], want []int) {
t.Helper()
inOrder := tree.InOrderTraverse()
if !sort.IntsAreSorted(inOrder) {
t.Fatalf("in-order traversal is not sorted: %v", inOrder)
}
for name, got := range map[string][]int{
"in-order": inOrder,
"pre-order": tree.PreOrderTraverse(),
"post-order": tree.PostOrderTraverse(),
"level-order": tree.LevelOrderTraverse(),
} {
sort.Ints(got)
if len(got) != len(want) {
t.Fatalf("%s traversal = %v, want values %v", name, got, want)
}
for i := range want {
if got[i] != want[i] {
t.Fatalf("%s traversal = %v, want values %v", name, got, want)
}
}
}
if len(want) == 0 && tree.Depth() != 0 {
t.Fatalf("empty tree depth = %d", tree.Depth())
}
}
+3 -1
View File
@@ -69,6 +69,9 @@ func inOrderTraverse[T any](node *datastructure.TreeNode[T]) []T {
// }
func levelOrderTraverse[T any](root *datastructure.TreeNode[T], traversal *[]T) {
if root == nil {
return
}
var q []*datastructure.TreeNode[T] // queue
var n *datastructure.TreeNode[T] // temp node
@@ -102,7 +105,6 @@ func insertTreeNode[T any](rootNode, newNode *datastructure.TreeNode[T], compara
}
}
// todo, delete root node failed
func deleteTreeNode[T any](node *datastructure.TreeNode[T], data T, comparator constraints.Comparator) *datastructure.TreeNode[T] {
if node == nil {
return nil