mirror of
https://github.com/duke-git/lancet.git
synced 2026-11-16 05:12:11 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8dd57ef490 | ||
|
|
7e02d7e4e6 | ||
|
|
9f7d939e5a | ||
|
|
03695c886c | ||
|
|
9c401c4043 | ||
|
|
f7acd40303 | ||
|
|
eb4b10cc15 |
No files matched your search
@@ -6,7 +6,6 @@
|
||||

|
||||
[](https://github.com/duke-git/lancet/releases)
|
||||
[](https://pkg.go.dev/github.com/duke-git/lancet/v2)
|
||||
[](https://goreportcard.com/report/github.com/duke-git/lancet/v2)
|
||||
[](https://github.com/duke-git/lancet/actions/workflows/codecov.yml)
|
||||
[](https://codecov.io/gh/duke-git/lancet)
|
||||
[](https://github.com/duke-git/lancet/blob/main/LICENSE)
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||

|
||||
[](https://github.com/duke-git/lancet/releases)
|
||||
[](https://pkg.go.dev/github.com/duke-git/lancet/v2)
|
||||
[](https://goreportcard.com/report/github.com/duke-git/lancet/v2)
|
||||
[](https://github.com/duke-git/lancet/actions/workflows/codecov.yml)
|
||||
[](https://codecov.io/gh/duke-git/lancet)
|
||||
[](https://github.com/duke-git/lancet/blob/main/LICENSE)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user