add trie/bst
Some checks failed
Lint / golangci-lint (push) Has been cancelled
Declarative Tests / test (push) Has been cancelled
Declarative Tests / build-fedora (push) Has been cancelled
Declarative Tests / build-ubuntu-focal (push) Has been cancelled

This commit is contained in:
Matthew Rich 2024-08-28 19:40:17 -07:00
parent 04b27dc5df
commit c3ea064bf0
4 changed files with 630 additions and 0 deletions

404
internal/search/binary.go Normal file
View File

@ -0,0 +1,404 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package search
import (
"log/slog"
"cmp"
"math"
)
var (
)
const (
BSTDATAOFFSET = 1
BSTROOTNODEID = 0
LEFTOFFSET = 1
RIGHTOFFSET = 2
)
/*
* Binary Search Tree using a slice
*/
type Binary[Key cmp.Ordered] []Key
type BinaryTree[Key cmp.Ordered] struct {
*Binary[Key]
zeroIndex int
least, greatest, lastNode int
}
func NewBinaryTree[Key cmp.Ordered](capacity int) (bst *BinaryTree[Key]) {
tree := make(Binary[Key], 1, capacity)
bst = &BinaryTree[Key]{ Binary: &tree }
return
}
func (b *Binary[Key]) Empty() Key {
return (*b)[0]
}
func (b *Binary[Key]) IsInternal(index int) bool {
childIdx := 2 * index
return ! b.IsEmpty(childIdx + LEFTOFFSET) && ! b.IsEmpty(childIdx + RIGHTOFFSET)
//return (*b)[leftIdx] != (*b)[0] && (*b)[leftIdx + 1] != (*b)[0]
}
func (b *Binary[Key]) IsRight(index int) bool {
return index % 2 == 0
}
func (b *Binary[Key]) Left(index int) int {
return (2 * index) + LEFTOFFSET
}
func (b *Binary[Key]) Right(index int) int {
return (2 * index) + RIGHTOFFSET
}
func (b *Binary[Key]) Get(index int) (Key) {
return (*b)[index + BSTDATAOFFSET]
}
func (b *Binary[Key]) Set(index int, v Key) {
(*b)[index + BSTDATAOFFSET] = v
}
func (b *BinaryTree[Key]) Set(index int, v Key) {
b.Binary.Set(index, v)
if v == b.Empty() {
b.setZeroIndex(index)
}
b.setLastNode(index)
}
func (b *Binary[Key]) SetEmpty(index int) {
(*b)[index + BSTDATAOFFSET] = b.Empty()
}
func (b *BinaryTree[Key]) SetEmpty(index int) {
b.Binary.SetEmpty(index)
if index == b.zeroIndex {
b.setZeroIndex(-1)
}
b.setLastNode(index)
}
func (b *Binary[Key]) SetEmptyRange(start int, end int) {
empty := b.Empty()
for i := start; i <= end; i++ {
(*b)[i + BSTDATAOFFSET] = empty
}
}
func (b *BinaryTree[Key]) SetEmptyRange(start int, end int) {
b.Binary.SetEmptyRange(start, end)
if b.zeroIndex >= start && b.zeroIndex <= end {
b.setZeroIndex(-1)
}
b.setLastNode(start)
}
func (b *BinaryTree[Key]) setZeroIndex(index int) {
b.zeroIndex = index
}
func (b *BinaryTree[Key]) setLastNode(index int) {
b.lastNode = max(b.lastNode, index)
}
func (b *BinaryTree[key]) LastNode() int {
return b.lastNode
}
func (b *BinaryTree[key]) MaxDepth() int {
return b.Depth(b.lastNode)
}
/*
func (b *Binary[Key]) Median(index int) {
length := len(*b)
}
*/
func (b *Binary[Key]) Copy(from int, to int) {
(*b)[to + BSTDATAOFFSET] = (*b)[from + BSTDATAOFFSET]
}
func (b *BinaryTree[Key]) Copy(from, to int) {
b.Binary.Copy(from, to)
if b.zeroIndex == from {
b.setZeroIndex(to)
}
slog.Info("Copy()", "from", from, "to", to, "value", b.Get(from), "zero", b.zeroIndex)
}
func (b *Binary[Key]) LevelRange(index int, treeIndex int) (start int, end int) {
treeDepth := b.Depth(treeIndex)
depth := b.Depth(index)
diff := depth - treeDepth
switch diff {
case 0:
if index == treeIndex {
return index, index
}
case 1:
leftIdx := (2 * treeIndex) + 1
if index >= leftIdx && index <= leftIdx + 1 {
return leftIdx, leftIdx + 1
}
default:
exp := int(math.Exp2(float64(diff)))
level := exp * treeIndex
childOffset := exp - 1
left := level + childOffset
right := level + (childOffset * 2)
return left, right
}
return -1, -1
}
func (b *Binary[Key]) InSubTree(index, treeIndex int) bool {
if treeIndex > index {
return false
}
left, right := b.LevelRange(index, treeIndex)
if index >= left && index <= right {
return true
}
return false
}
func (b *BinaryTree[Key]) MoveSubTree(from, to int) {
if ! b.IsLeaf(to) {
panic("this move would overwrite the target subtree")
}
srccursor := b.Left(from)
dstcursor := b.Left(to)
for srccursor < b.lastNode && srccursor < len(*b.Binary) {
srcstart, srcend := b.LevelRange(srccursor, from)
dststart, dstend := b.LevelRange(dstcursor, to)
slog.Info("MoveSubTree()", "srcstart", srcstart, "srcend", srcend, "dststart", dststart, "dstend", dstend, "source", (*b.Binary)[srcstart + BSTDATAOFFSET:srcend + BSTDATAOFFSET + 1], "dest", (*b.Binary)[dststart + BSTDATAOFFSET:dstend + BSTDATAOFFSET + 1])
copy((*b.Binary)[dststart + BSTDATAOFFSET:dstend + BSTDATAOFFSET + 1], (*b.Binary)[srcstart + BSTDATAOFFSET:srcend + BSTDATAOFFSET + 1])
b.SetEmptyRange(srcstart, srcend)
srccursor = b.Left(srccursor)
dstcursor = b.Left(dstcursor)
slog.Info("MoveSubTree() post-copy", "srcstart", srcstart, "srcend", srcend, "dststart", dststart, "dstend", dstend, "data", b.Binary)
}
b.Copy(from, to)
}
func (b *Binary[Key]) IsLeaf(index int) bool {
childIdx := 2 * (index)
return (*b)[childIdx + LEFTOFFSET + BSTDATAOFFSET] == (*b)[childIdx + RIGHTOFFSET + BSTDATAOFFSET]
}
func (b *BinaryTree[Key]) IsLeaf(index int) bool {
childIdx := 2 * (index)
leftIdx := childIdx + LEFTOFFSET
rightIdx := childIdx + RIGHTOFFSET
return b.Get(leftIdx) == b.Get(rightIdx) && leftIdx != b.zeroIndex && rightIdx != b.zeroIndex
}
func (b *Binary[Key]) ParentIndex(index int) (parent int) {
parent = index
if b.IsRight(index) {
parent -= RIGHTOFFSET
} else {
parent -= LEFTOFFSET
}
parent = parent / 2
return
}
func (b *Binary[Key]) Depth(index int) int {
return int(math.Ceil(math.Log2(float64(index + 2))))
}
func (b *Binary[Key]) Width(index int) int {
return int(math.Exp2(float64(b.Depth(index) - 1)))
}
func (b *Binary[Key]) IsFullLevel(index int) bool {
width := b.Width(index)
start := width - 1
for _, v := range (*b)[start + BSTDATAOFFSET: start + width + BSTDATAOFFSET] {
if v == b.Empty() {
return false
}
}
return true
}
func (b *Binary[Key]) IsEmpty(index int) bool {
return (*b)[index + BSTDATAOFFSET] == b.Empty()
}
func (b *Binary[Key]) Ascend(idx int, item Key) int {
if item < b.Get(idx) {
return b.Left(idx)
}
return b.Right(idx)
}
func (b *Binary[Key]) Grow(index int) {
size := len(*b) - BSTDATAOFFSET
slog.Info("Grow()", "size", size, "index", index, "cap", cap(*b))
if index >= size / 2 {
targetSize := (size + (2 * index + 1) * 3) + BSTDATAOFFSET
if cap(*b) < targetSize {
expandArray := make(Binary[Key], ((targetSize - cap(*b)) * 2) + BSTDATAOFFSET)
*b = append(*b, expandArray...)
slog.Info("Grow()", "size", size, "index", index, "targetsize", targetSize, "cap", cap(*b), "new", cap(expandArray))
}
*b = (*b)[:targetSize]
}
slog.Info("Grow()", "size", len(*b))
}
func (b *BinaryTree[Key]) RotateRight(index, right, left int) int {
slog.Info("PreRotateRight", "index", index, "leftindex", left, "rightindex", right, "left", b.Get(left), "node", b.Get(index), "right", b.Get(right))
b.Copy(index, right)
b.Copy(left, index)
b.SetEmpty(left)
slog.Info("RotateRight", "index", index, "leftindex", left, "rightindex", right, "left", b.Get(left), "node", b.Get(index), "right", b.Get(right))
return left
}
func (b *BinaryTree[Key]) RotateLeft(index, left, right int) int {
slog.Info("PreRotateLeft", "index", index, "leftindex", left, "rightindex", right, "left", b.Get(left), "node", b.Get(index), "right", b.Get(right))
b.Copy(index, left)
b.Copy(right, index)
b.SetEmpty(right)
slog.Info("RotateLeft", "index", index, "leftindex", left, "rightindex", right, "left", b.Get(left), "node", b.Get(index), "right", b.Get(right))
return right
}
/*
func (b *Binary[Key]) Insert(item Key) (idx int) {
idx = BSTROOTNODEID
slog.Info("Insert()", "item", item)
for {
b.Grow(idx)
if b.IsEmpty(idx) {
b.Set(idx, item)
return
} else {
rightIdx := b.Right(idx)
leftIdx := b.Left(idx)
if b.IsInternal(idx) {
idx = b.Ascend(idx, item)
} else if b.IsLeaf(idx) {
idx = b.Ascend(idx, item)
} else {
slog.Info("Insert()", "item", item, "index", idx)
if item < b.Get(idx) {
if ! b.IsEmpty(rightIdx) {
idx = leftIdx
} else {
if ! b.IsLeaf(leftIdx) {
panic("the left node is not a leaf")
}
idx = b.RotateRight(idx, rightIdx, leftIdx)
}
} else {
if ! b.IsEmpty(leftIdx) {
idx = rightIdx
} else {
if ! b.IsLeaf(rightIdx) {
panic("the right node is not a leaf")
}
idx = b.RotateLeft(idx, leftIdx, rightIdx)
}
}
}
}
}
}
*/
func (b *BinaryTree[Key]) Insert(item Key) (idx int) {
idx = BSTROOTNODEID
slog.Info("Insert()", "item", item)
for {
b.Grow(idx)
if b.IsEmpty(idx) {
b.Set(idx, item)
return
} else {
rightIdx := b.Right(idx)
leftIdx := b.Left(idx)
if b.IsInternal(idx) {
idx = b.Ascend(idx, item)
} else if b.IsLeaf(idx) {// && b.IsFullLevel(idx) {
/*
if ! b.IsFullLevel(idx) {
parent := b.ParentIndex(idx)
if b.IsRight(parent) {
idx = b.RotateLeft(parent, b.Left(parent), b.Right(parent))
} else {
idx = b.RotateRight(parent, b.Right(parent), b.Left(parent))
}
}
*/
idx = b.Ascend(idx, item)
} else {
slog.Info("Insert()", "item", item, "index", idx)
if item < b.Get(idx) { // left
slog.Info("Insert()", "item", item, "index", idx)
if ! b.IsEmpty(rightIdx) {
idx = leftIdx
} else {
if ! b.IsLeaf(leftIdx) {
panic("the left node is not a leaf")
}
idx = b.RotateRight(idx, rightIdx, leftIdx)
}
} else { // right
if ! b.IsEmpty(leftIdx) {
idx = rightIdx
} else {
if ! b.IsLeaf(rightIdx) {
idx = rightIdx
//panic("the right node is not a leaf")
} else {
idx = b.RotateLeft(idx, leftIdx, rightIdx)
}
}
}
}
}
}
}
type BinaryBFSItem[Key cmp.Ordered] func(index int, depth int, v Key)
func (b *Binary[Key]) BFS(c BinaryBFSItem[Key]) (depth int) {
queue := []int{ BSTROOTNODEID }
for len(queue) > 0 {
x := queue[0]
queue = queue[1:]
depth = b.Depth(x)
c(x, depth, b.Get(x))
leftIdx := b.Left(x)
rightIdx := b.Right(x)
if ! b.IsEmpty(leftIdx) {
queue = append(queue, leftIdx)
}
if ! b.IsEmpty(rightIdx) {
queue = append(queue, b.Right(x))
}
}
return
}

View File

@ -0,0 +1,117 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package search
import (
"github.com/stretchr/testify/assert"
"testing"
"log/slog"
)
func TestNewBinary(t *testing.T) {
b := NewBinaryTree[int](9)
assert.NotNil(t, b)
for _, v := range []struct { expectedindex , value int } {
{ expectedindex: BSTROOTNODEID, value: 7 },
{ expectedindex: 2*BSTROOTNODEID + LEFTOFFSET, value: 4 },
{ expectedindex: 2*BSTROOTNODEID + RIGHTOFFSET, value: 9 },
{ expectedindex: 4 + RIGHTOFFSET, value: 18 },
{ expectedindex: 2 + RIGHTOFFSET, value: 5 },
{ expectedindex: 4 + RIGHTOFFSET, value: 20 },
{ expectedindex: 4 - LEFTOFFSET, value: 3 },
{ expectedindex: 8 - LEFTOFFSET, value: 2 },
{ expectedindex: 8 - LEFTOFFSET, value: 1 },
{ expectedindex: 16 - LEFTOFFSET, value: 0 },
{ expectedindex: 16 - LEFTOFFSET, value: -1 },
} {
idx := b.Insert(v.value)
slog.Info("TestInsert()", "index", idx, "value", v, "b", b, "data", b.Binary)
assert.Equal(t, v.expectedindex, idx)
assert.Equal(t, v.value, b.Get(v.expectedindex))
}
assert.Equal(t, 7, b.zeroIndex)
b.BFS(func(index int, depth int, v int) {
slog.Info("BFS()", "index", index, "depth", depth, "value", v, "b", b, "data", b.Binary)
assert.Equal(t, v, b.Get(index))
})
}
func TestBinaryDepth(t *testing.T) {
b := NewBinaryTree[int](9)
assert.NotNil(t, b)
for _, v := range []struct{ expecteddepth, index int }{
{ expecteddepth: 1, index: 0 },
{ expecteddepth: 2, index: 1 },
{ expecteddepth: 2, index: 2 },
{ expecteddepth: 3, index: 5 },
} {
assert.Equal(t, v.expecteddepth, b.Depth(v.index))
}
}
func TestBinaryWidth(t *testing.T) {
b := NewBinaryTree[int](9)
assert.NotNil(t, b)
for _, v := range []struct{ expectedwidth, index int }{
{ expectedwidth: 1, index: 0 },
{ expectedwidth: 2, index: 1 },
{ expectedwidth: 2, index: 2 },
{ expectedwidth: 4, index: 5 },
{ expectedwidth: 8, index: 8 },
{ expectedwidth: 16, index: 17 },
} {
assert.Equal(t, v.expectedwidth, b.Width(v.index))
}
}
func TestBinaryInSubTree(t *testing.T) {
b := NewBinaryTree[int](9)
assert.NotNil(t, b)
for _, v := range []struct{ expected bool; tree, index, start, end int }{
{ expected: true, tree: 0, index: 0, start: 0, end: 0 },
{ expected: true, tree: 4, index: 19, start: 19, end: 22 },
{ expected: false, tree: 4, index: 13, start: -1, end: -1 },
{ expected: false, tree: 5, index: 6, start: -1, end: -1 },
} {
start, end := b.LevelRange(v.index, v.tree)
assert.Equal(t, v.start, start)
assert.Equal(t, v.end, end)
assert.Equal(t, v.expected, b.InSubTree(v.index, v.tree))
}
}
func TestBinaryMoveSubTree(t *testing.T) {
expected := []int{ 0, 4, 0, 7, 0, 0, 5, 18, 0, 0, 0, 0, 0, 0, 9, 20 }
b := NewBinaryTree[int](9)
assert.NotNil(t, b)
b.Insert(7)
b.Insert(4)
b.Insert(9)
b.Insert(18)
b.Insert(20)
b.MoveSubTree(2, 6)
b.RotateRight(0, 2, 1)
b.Insert(5)
assert.EqualValues(t, expected, (*b.Binary)[:16])
}
func TestBinaryMaxDepth(t *testing.T) {
//expected := []int{ 0, 4, 0, 7, 0, 0, 5, 18, 0, 0, 0, 0, 0, 0, 9, 20 }
b := NewBinaryTree[int](9)
assert.NotNil(t, b)
b.Insert(7)
b.Insert(4)
b.Insert(9)
b.Insert(18)
b.Insert(20)
b.Insert(5)
b.Insert(11)
b.Insert(-1)
b.Insert(33)
b.Insert(15)
b.Insert(13)
slog.Info("TestBinaryMaxDepth", "maxdepth", b.MaxDepth(), "last", b.LastNode(), "data", (*b.Binary))
}

93
internal/search/trie.go Normal file
View File

@ -0,0 +1,93 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package search
import (
)
var (
AlphabetSize uint = 26
FirstAlphabetChar = uint8('a')
)
type String string
type TrieNode struct {
Children []*TrieNode
Words int
}
func NewTrieNode() *TrieNode {
return &TrieNode{ Children: make([]*TrieNode, AlphabetSize), Words: 0 }
}
func (r *TrieNode) Insert(key string) {
current := r
for _, c := range key {
charIndex := uint8(c) - FirstAlphabetChar
if current.Children[charIndex] == nil {
current.Children[charIndex] = NewTrieNode()
}
current = current.Children[charIndex]
}
current.Words++
}
func (s String) Index(root *TrieNode) {
root.Insert(string(s))
}
func (r *TrieNode) Search(key string) bool {
current := r
for _, c := range key {
charIndex := uint8(c) - FirstAlphabetChar
if current.Children[charIndex] == nil {
return false
}
current = current.Children[charIndex]
}
return (current.Words > 0)
}
func (r *TrieNode) Delete(word string) bool {
current := r
var last *TrieNode = nil
var lastChar rune
for _, c := range word {
charIndex := uint8(c) - FirstAlphabetChar
if current.Children[charIndex] == nil {
return false
} else {
count := 0
for i := uint(0); i < AlphabetSize; i++ {
if current.Children[i] != nil {
count++
}
}
if count > 1 {
last = current
lastChar = c
}
current = current.Children[charIndex]
}
}
count := 0
for i := uint(0); i < AlphabetSize; i++ {
if current.Children[i] != nil {
count++
}
}
if (count > 0) { // is prefix
current.Words--
return true
}
if last != nil { // shares a common prefix with other words
last.Children[lastChar] = nil
return true
} else { // does not share a common prefix
r.Children[word[0]] = nil
return true
}
}

View File

@ -0,0 +1,16 @@
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package search
import (
"github.com/stretchr/testify/assert"
"testing"
_ "log/slog"
)
func TestNewTrie(t *testing.T) {
n := NewTrieNode()
assert.NotNil(t, n)
}