From b1a487cae8643014e30d997c022efe898388587f Mon Sep 17 00:00:00 2001 From: BhuvignaReddy A T Date: Thu, 19 Mar 2026 12:13:03 +0530 Subject: [PATCH 01/26] chore:Initializing project structure and node files --- internal/art/art.go | 3 +++ internal/art/leaf.go | 3 +++ internal/art/node.go | 3 +++ internal/art/node16.go | 3 +++ internal/art/node256.go | 3 +++ internal/art/node4.go | 3 +++ internal/art/node48.go | 3 +++ internal/art/util.go | 3 +++ 8 files changed, 24 insertions(+) create mode 100644 internal/art/art.go create mode 100644 internal/art/leaf.go create mode 100644 internal/art/node.go create mode 100644 internal/art/node16.go create mode 100644 internal/art/node256.go create mode 100644 internal/art/node4.go create mode 100644 internal/art/node48.go create mode 100644 internal/art/util.go diff --git a/internal/art/art.go b/internal/art/art.go new file mode 100644 index 0000000..8817c5e --- /dev/null +++ b/internal/art/art.go @@ -0,0 +1,3 @@ +package art + +// TODO: Public API (Tree struct, Insert, Search, Delete) diff --git a/internal/art/leaf.go b/internal/art/leaf.go new file mode 100644 index 0000000..5dc864b --- /dev/null +++ b/internal/art/leaf.go @@ -0,0 +1,3 @@ +package art + +// TODO: Leaf node structure for storing values diff --git a/internal/art/node.go b/internal/art/node.go new file mode 100644 index 0000000..3ab03e9 --- /dev/null +++ b/internal/art/node.go @@ -0,0 +1,3 @@ +package art + +// TODO: Interfaces and shared node header (meta) diff --git a/internal/art/node16.go b/internal/art/node16.go new file mode 100644 index 0000000..85b049d --- /dev/null +++ b/internal/art/node16.go @@ -0,0 +1,3 @@ +package art + +// TODO: Node16 implementation diff --git a/internal/art/node256.go b/internal/art/node256.go new file mode 100644 index 0000000..7079796 --- /dev/null +++ b/internal/art/node256.go @@ -0,0 +1,3 @@ +package art + +// TODO: Node256 implementation (direct map) diff --git a/internal/art/node4.go b/internal/art/node4.go new file mode 100644 index 0000000..6fcbc5d --- /dev/null +++ b/internal/art/node4.go @@ -0,0 +1,3 @@ +package art + +// TODO: Node4 implementation diff --git a/internal/art/node48.go b/internal/art/node48.go new file mode 100644 index 0000000..43de596 --- /dev/null +++ b/internal/art/node48.go @@ -0,0 +1,3 @@ +package art + +// TODO: Node48 implementation (indirection layer) diff --git a/internal/art/util.go b/internal/art/util.go new file mode 100644 index 0000000..d82e98d --- /dev/null +++ b/internal/art/util.go @@ -0,0 +1,3 @@ +package art + +// TODO: Helper functions (e.g., prefix matching) From 058b2e5d1c82650a570b64e2131a87d7cd5789af Mon Sep 17 00:00:00 2001 From: angelo Date: Thu, 19 Mar 2026 22:10:24 +0530 Subject: [PATCH 02/26] feat:insert+utility functions --- cmd/radFS/arttest/main.go | 15 +++++++++++ internal/art/art.go | 12 +++++++++ internal/art/insert.go | 51 +++++++++++++++++++++++++++++++++++++ internal/art/insert_test.go | 41 +++++++++++++++++++++++++++++ internal/art/leaf.go | 19 ++++++++++++++ internal/art/node.go | 30 ++++++++++++++++++++++ internal/art/node4.go | 13 +++++++++- internal/art/print_tree.go | 38 +++++++++++++++++++++++++++ internal/art/util.go | 32 +++++++++++++++++++++++ 9 files changed, 250 insertions(+), 1 deletion(-) create mode 100644 cmd/radFS/arttest/main.go create mode 100644 internal/art/insert.go create mode 100644 internal/art/insert_test.go create mode 100644 internal/art/print_tree.go diff --git a/cmd/radFS/arttest/main.go b/cmd/radFS/arttest/main.go new file mode 100644 index 0000000..eda49b2 --- /dev/null +++ b/cmd/radFS/arttest/main.go @@ -0,0 +1,15 @@ +package main + +import ( + "github.com/acmpesuecc/radFS/internal/art" +) + +func main() { + var t art.Tree + + t.Insert([]byte("cat"), "v1") + t.Insert([]byte("car"), "v2") + t.Insert([]byte("cap"), "v3") + + art.PrintTree(t.Root(), 0) +} diff --git a/internal/art/art.go b/internal/art/art.go index 8817c5e..7a1cd36 100644 --- a/internal/art/art.go +++ b/internal/art/art.go @@ -1,3 +1,15 @@ package art // TODO: Public API (Tree struct, Insert, Search, Delete) + +type Tree struct { + root *Node +} + +func (t *Tree) Insert(key []byte, value string) { + t.root = insert(t.root, value, key, 0) +} + +func (t *Tree) Root() *Node { + return t.root +} diff --git a/internal/art/insert.go b/internal/art/insert.go new file mode 100644 index 0000000..7d31eba --- /dev/null +++ b/internal/art/insert.go @@ -0,0 +1,51 @@ +package art + +func insert(n *Node, value string, key []byte, depth int) *Node { + + if n == nil { + return newleaf(value, key) + } + if isleaf(n) { + new_node := newNode4() + oldkey := n.leaf.key + i := depth + for i < len(oldkey) && i < len(key) && oldkey[i] == key[i] { + new_node.innerNode.meta.prefix[i-depth] = key[i] + i++ + } + + new_node.innerNode.meta.prefixlen = i - depth + depth = i + + addchild(new_node, key[depth], newleaf(value, key)) + addchild(new_node, oldkey[depth], n) + return new_node + + } + p := checkprefix(n, key, depth) + if p != n.innerNode.meta.prefixlen { + new_node := newNode4() + addchild(new_node, key[depth+p], newleaf(value, key)) + addchild(new_node, n.innerNode.meta.prefix[p], n) + new_node.innerNode.meta.prefixlen = p + copy(new_node.innerNode.meta.prefix, n.innerNode.meta.prefix[:p]) + + oldprefixlen := n.innerNode.meta.prefixlen + n.innerNode.meta.prefixlen = n.innerNode.meta.prefixlen - (p + 1) + copy(n.innerNode.meta.prefix, n.innerNode.meta.prefix[p+1:oldprefixlen]) + return new_node + } + + depth += n.innerNode.meta.prefixlen + next, pos := findchild(key[depth], n) + if next != nil { + n.innerNode.children[pos] = insert(next, value, key, depth+1) + return n + + } else { + addchild(n, key[depth], newleaf(value, key)) + return n + + } + +} diff --git a/internal/art/insert_test.go b/internal/art/insert_test.go new file mode 100644 index 0000000..c3e9932 --- /dev/null +++ b/internal/art/insert_test.go @@ -0,0 +1,41 @@ +package art + +import "testing" + +func TestInsertStructure(t *testing.T) { + var tree Tree + + tree.Insert([]byte("cat"), "v1") + tree.Insert([]byte("car"), "v2") + tree.Insert([]byte("cap"), "v3") + + root := tree.root + + if root == nil { + t.Fatal("root nil") + } + + if isleaf(root) { + t.Fatal("root should not be leaf") + } + + in := root.innerNode + + // check prefix + prefix := string(in.meta.prefix[:in.meta.prefixlen]) + if prefix != "ca" { + t.Fatalf("expected prefix 'ca', got '%s'", prefix) + } + + // check children count + count := 0 + for _, c := range in.children { + if c != nil { + count++ + } + } + + if count != 3 { + t.Fatalf("expected 3 children, got %d", count) + } +} diff --git a/internal/art/leaf.go b/internal/art/leaf.go index 5dc864b..48c1a62 100644 --- a/internal/art/leaf.go +++ b/internal/art/leaf.go @@ -1,3 +1,22 @@ package art // TODO: Leaf node structure for storing values + +type leaf struct { + key []byte + values string +} + +func newleaf(value string, key []byte) *Node { + return &Node{ + leaf: &leaf{key: key, values: value}, + } + +} +func isleaf(n *Node) bool { + if n.leaf != nil { + return true + } else { + return false + } +} diff --git a/internal/art/node.go b/internal/art/node.go index 3ab03e9..99f720d 100644 --- a/internal/art/node.go +++ b/internal/art/node.go @@ -1,3 +1,33 @@ package art // TODO: Interfaces and shared node header (meta) + +type NodeType int + +const ( + Node4 NodeType = iota + Node16 + Node48 + Node256 +) +const ( + Node4max = 4 + maxprefixlen = 8 +) + +type Node struct { + innerNode *innerNode + leaf *leaf +} + +type innerNode struct { + nodeType NodeType + keys []byte + children []*Node + meta meta +} + +type meta struct { + prefix []byte + prefixlen int +} diff --git a/internal/art/node4.go b/internal/art/node4.go index 6fcbc5d..1e154c2 100644 --- a/internal/art/node4.go +++ b/internal/art/node4.go @@ -1,3 +1,14 @@ package art -// TODO: Node4 implementation +func newNode4() *Node { + in := &innerNode{ + nodeType: Node4, + keys: make([]byte, Node4max), + children: make([]*Node, Node4max), + meta: meta{ + prefix: make([]byte, maxprefixlen), + }, + } + return &Node{innerNode: in} + +} diff --git a/internal/art/print_tree.go b/internal/art/print_tree.go new file mode 100644 index 0000000..f3a98ef --- /dev/null +++ b/internal/art/print_tree.go @@ -0,0 +1,38 @@ +package art + +import "fmt" + +func PrintTree(n *Node, level int) { + if n == nil { + return + } + + indent := "" + for i := 0; i < level; i++ { + indent += " " + } + + if isleaf(n) { + fmt.Println(indent + "Leaf: " + string(n.leaf.key)) + return + } + + in := n.innerNode + + prefixLen := in.meta.prefixlen + if prefixLen < 0 || prefixLen > len(in.meta.prefix) { + prefixLen = 0 + } + + prefix := string(in.meta.prefix[:prefixLen]) + + fmt.Println(indent+"Node(prefix=\""+prefix+"\", prefixLen=", prefixLen, ")") + + // Print children + for i := 0; i < len(in.keys); i++ { + if in.children[i] != nil { + fmt.Println(indent+" Edge('", string(in.keys[i]), "'):") + PrintTree(in.children[i], level+1) + } + } +} diff --git a/internal/art/util.go b/internal/art/util.go index d82e98d..5e48f23 100644 --- a/internal/art/util.go +++ b/internal/art/util.go @@ -1,3 +1,35 @@ package art // TODO: Helper functions (e.g., prefix matching) +func addchild(n *Node, k byte, child *Node) { + for i := 0; i < len(n.innerNode.keys); i++ { + if n.innerNode.children[i] == nil { + n.innerNode.children[i] = child + n.innerNode.keys[i] = k + return + + } + + } + +} +func checkprefix(n *Node, key []byte, depth int) int { + in := n.innerNode + var i int + for i = 0; i < in.meta.prefixlen && in.meta.prefix[i] == key[depth+i]; i++ { //checks prefix until mismatch + + } + return i + +} +func findchild(k byte, n *Node) (*Node, int) { + in := n.innerNode + for i := 0; i < len(in.keys); i++ { + if in.keys[i] == k { + return in.children[i], i //finds the node and the position + } + + } + return nil, -1 + +} From 3577de901b24aca9a53f20a2d3feab31b4d80579 Mon Sep 17 00:00:00 2001 From: BhuvignaReddy A T Date: Fri, 20 Mar 2026 13:39:14 +0530 Subject: [PATCH 03/26] Feat: Add Search/lookup function and remove insert.go --- cmd/radFS/arttest/main.go | 10 +++- internal/art/art.go | 98 ++++++++++++++++++++++++++++++++++++++- internal/art/insert.go | 51 -------------------- 3 files changed, 105 insertions(+), 54 deletions(-) delete mode 100644 internal/art/insert.go diff --git a/cmd/radFS/arttest/main.go b/cmd/radFS/arttest/main.go index eda49b2..980c29b 100644 --- a/cmd/radFS/arttest/main.go +++ b/cmd/radFS/arttest/main.go @@ -9,7 +9,15 @@ func main() { t.Insert([]byte("cat"), "v1") t.Insert([]byte("car"), "v2") - t.Insert([]byte("cap"), "v3") + t.Insert([]byte("cab"), "v3") + v, ok := t.Search([]byte("cat")) + println("cat:", v, ok) + + v, ok = t.Search([]byte("cab")) + println("cab:", v, ok) + + v, ok = t.Search([]byte("cart")) + println("cart:", v, ok) art.PrintTree(t.Root(), 0) } diff --git a/internal/art/art.go b/internal/art/art.go index 7a1cd36..2a3fbec 100644 --- a/internal/art/art.go +++ b/internal/art/art.go @@ -6,10 +6,104 @@ type Tree struct { root *Node } +func (t *Tree) Root() *Node { + return t.root +} + func (t *Tree) Insert(key []byte, value string) { t.root = insert(t.root, value, key, 0) } -func (t *Tree) Root() *Node { - return t.root +func insert(n *Node, value string, key []byte, depth int) *Node { + + if n == nil { + return newleaf(value, key) + } + if isleaf(n) { + new_node := newNode4() + oldkey := n.leaf.key + i := depth + for i < len(oldkey) && i < len(key) && oldkey[i] == key[i] { + new_node.innerNode.meta.prefix[i-depth] = key[i] + i++ + } + + new_node.innerNode.meta.prefixlen = i - depth + depth = i + + addchild(new_node, key[depth], newleaf(value, key)) + addchild(new_node, oldkey[depth], n) + return new_node + + } + p := checkprefix(n, key, depth) + if p != n.innerNode.meta.prefixlen { + new_node := newNode4() + addchild(new_node, key[depth+p], newleaf(value, key)) + addchild(new_node, n.innerNode.meta.prefix[p], n) + new_node.innerNode.meta.prefixlen = p + copy(new_node.innerNode.meta.prefix, n.innerNode.meta.prefix[:p]) + + oldprefixlen := n.innerNode.meta.prefixlen + n.innerNode.meta.prefixlen = n.innerNode.meta.prefixlen - (p + 1) + copy(n.innerNode.meta.prefix, n.innerNode.meta.prefix[p+1:oldprefixlen]) + return new_node + } + + depth += n.innerNode.meta.prefixlen + next, pos := findchild(key[depth], n) + if next != nil { + n.innerNode.children[pos] = insert(next, value, key, depth+1) + return n + + } else { + addchild(n, key[depth], newleaf(value, key)) + return n + + } + +} + +func (t *Tree) Search(key []byte) (string, bool) { + leaf := search(t.root, key, 0) // start from root and depth 0 + if leaf != nil && isleaf(leaf) { + return leaf.leaf.values, true //Node->innerleaf->values + } + return "", false +} + +func search(n *Node, key []byte, depth int) *Node { + if n == nil { + return nil + } + + if isleaf(n) { + // Verify if the leaf's key actually matches our search key + if string(n.leaf.key) == string(key) { + return n + } + return nil + } + + // 1. Check if the node's prefix matches the current part of the key + if n.innerNode.meta.prefixlen > 0 { + p := checkprefix(n, key, depth) + if p != n.innerNode.meta.prefixlen { + return nil + } + depth += n.innerNode.meta.prefixlen + } + + // 2. Bound check: if we've consumed the prefix but the key is finished, and we aren't at a leaf, the key doesn't exist. + if depth >= len(key) { + return nil + } + + // 3. Find the child corresponding to the byte at the current depth + next, _ := findchild(key[depth], n) + if next != nil { + return search(next, key, depth+1) + } + + return nil } diff --git a/internal/art/insert.go b/internal/art/insert.go deleted file mode 100644 index 7d31eba..0000000 --- a/internal/art/insert.go +++ /dev/null @@ -1,51 +0,0 @@ -package art - -func insert(n *Node, value string, key []byte, depth int) *Node { - - if n == nil { - return newleaf(value, key) - } - if isleaf(n) { - new_node := newNode4() - oldkey := n.leaf.key - i := depth - for i < len(oldkey) && i < len(key) && oldkey[i] == key[i] { - new_node.innerNode.meta.prefix[i-depth] = key[i] - i++ - } - - new_node.innerNode.meta.prefixlen = i - depth - depth = i - - addchild(new_node, key[depth], newleaf(value, key)) - addchild(new_node, oldkey[depth], n) - return new_node - - } - p := checkprefix(n, key, depth) - if p != n.innerNode.meta.prefixlen { - new_node := newNode4() - addchild(new_node, key[depth+p], newleaf(value, key)) - addchild(new_node, n.innerNode.meta.prefix[p], n) - new_node.innerNode.meta.prefixlen = p - copy(new_node.innerNode.meta.prefix, n.innerNode.meta.prefix[:p]) - - oldprefixlen := n.innerNode.meta.prefixlen - n.innerNode.meta.prefixlen = n.innerNode.meta.prefixlen - (p + 1) - copy(n.innerNode.meta.prefix, n.innerNode.meta.prefix[p+1:oldprefixlen]) - return new_node - } - - depth += n.innerNode.meta.prefixlen - next, pos := findchild(key[depth], n) - if next != nil { - n.innerNode.children[pos] = insert(next, value, key, depth+1) - return n - - } else { - addchild(n, key[depth], newleaf(value, key)) - return n - - } - -} From 68dbdbe870d5191b426dbb0f3cb1c3fb638dd9f2 Mon Sep 17 00:00:00 2001 From: angelo Date: Fri, 20 Mar 2026 19:33:14 +0530 Subject: [PATCH 04/26] chore: fixed bug 'variable size keys index out of bounds error' --- cmd/radFS/arttest/main.go | 7 +++--- internal/art/art.go | 50 +++++++++++++++++++++++++++++++++++++ internal/art/insert.go | 51 -------------------------------------- internal/art/print_tree.go | 12 ++++----- internal/art/util.go | 11 +++++++- 5 files changed, 70 insertions(+), 61 deletions(-) delete mode 100644 internal/art/insert.go diff --git a/cmd/radFS/arttest/main.go b/cmd/radFS/arttest/main.go index eda49b2..4255ada 100644 --- a/cmd/radFS/arttest/main.go +++ b/cmd/radFS/arttest/main.go @@ -7,9 +7,10 @@ import ( func main() { var t art.Tree - t.Insert([]byte("cat"), "v1") - t.Insert([]byte("car"), "v2") - t.Insert([]byte("cap"), "v3") + t.Insert([]byte("cats"), "v1") + + t.Insert([]byte("cat"), "v3") + t.Insert([]byte("carpet"), "v3") art.PrintTree(t.Root(), 0) } diff --git a/internal/art/art.go b/internal/art/art.go index 7a1cd36..31f6095 100644 --- a/internal/art/art.go +++ b/internal/art/art.go @@ -13,3 +13,53 @@ func (t *Tree) Insert(key []byte, value string) { func (t *Tree) Root() *Node { return t.root } + +func insert(n *Node, value string, key []byte, depth int) *Node { + + if n == nil { + return newleaf(value, key) + } + if isleaf(n) { + new_node := newNode4() + oldkey := n.leaf.key + i := depth + for i < len(oldkey) && i < len(key) && oldkey[i] == key[i] { + new_node.innerNode.meta.prefix[i-depth] = key[i] + i++ + } + + new_node.innerNode.meta.prefixlen = i - depth + depth = i + + addchild(new_node, keycheck(key, depth), newleaf(value, key)) + addchild(new_node, keycheck(oldkey, depth), n) + return new_node + + } + p := checkprefix(n, key, depth) + if p != n.innerNode.meta.prefixlen { + new_node := newNode4() + addchild(new_node, keycheck(key, depth+p), newleaf(value, key)) + addchild(new_node, n.innerNode.meta.prefix[p], n) + new_node.innerNode.meta.prefixlen = p + copy(new_node.innerNode.meta.prefix, n.innerNode.meta.prefix[:p]) + + oldprefixlen := n.innerNode.meta.prefixlen + n.innerNode.meta.prefixlen = n.innerNode.meta.prefixlen - (p + 1) + copy(n.innerNode.meta.prefix, n.innerNode.meta.prefix[p+1:oldprefixlen]) + return new_node + } + + depth += n.innerNode.meta.prefixlen + next, pos := findchild(keycheck(key, depth), n) + if next != nil { + n.innerNode.children[pos] = insert(next, value, key, depth+1) + return n + + } else { + addchild(n, keycheck(key, depth), newleaf(value, key)) + return n + + } + +} diff --git a/internal/art/insert.go b/internal/art/insert.go deleted file mode 100644 index 7d31eba..0000000 --- a/internal/art/insert.go +++ /dev/null @@ -1,51 +0,0 @@ -package art - -func insert(n *Node, value string, key []byte, depth int) *Node { - - if n == nil { - return newleaf(value, key) - } - if isleaf(n) { - new_node := newNode4() - oldkey := n.leaf.key - i := depth - for i < len(oldkey) && i < len(key) && oldkey[i] == key[i] { - new_node.innerNode.meta.prefix[i-depth] = key[i] - i++ - } - - new_node.innerNode.meta.prefixlen = i - depth - depth = i - - addchild(new_node, key[depth], newleaf(value, key)) - addchild(new_node, oldkey[depth], n) - return new_node - - } - p := checkprefix(n, key, depth) - if p != n.innerNode.meta.prefixlen { - new_node := newNode4() - addchild(new_node, key[depth+p], newleaf(value, key)) - addchild(new_node, n.innerNode.meta.prefix[p], n) - new_node.innerNode.meta.prefixlen = p - copy(new_node.innerNode.meta.prefix, n.innerNode.meta.prefix[:p]) - - oldprefixlen := n.innerNode.meta.prefixlen - n.innerNode.meta.prefixlen = n.innerNode.meta.prefixlen - (p + 1) - copy(n.innerNode.meta.prefix, n.innerNode.meta.prefix[p+1:oldprefixlen]) - return new_node - } - - depth += n.innerNode.meta.prefixlen - next, pos := findchild(key[depth], n) - if next != nil { - n.innerNode.children[pos] = insert(next, value, key, depth+1) - return n - - } else { - addchild(n, key[depth], newleaf(value, key)) - return n - - } - -} diff --git a/internal/art/print_tree.go b/internal/art/print_tree.go index f3a98ef..ea36b6d 100644 --- a/internal/art/print_tree.go +++ b/internal/art/print_tree.go @@ -19,19 +19,19 @@ func PrintTree(n *Node, level int) { in := n.innerNode - prefixLen := in.meta.prefixlen - if prefixLen < 0 || prefixLen > len(in.meta.prefix) { - prefixLen = 0 + prefixlen := in.meta.prefixlen + if prefixlen < 0 || prefixlen > len(in.meta.prefix) { + prefixlen = 0 } - prefix := string(in.meta.prefix[:prefixLen]) + prefix := string(in.meta.prefix[:prefixlen]) - fmt.Println(indent+"Node(prefix=\""+prefix+"\", prefixLen=", prefixLen, ")") + fmt.Println(indent+"Node(prefix=\""+prefix+"\", prefixLen=", prefixlen, ")") // Print children for i := 0; i < len(in.keys); i++ { if in.children[i] != nil { - fmt.Println(indent+" Edge('", string(in.keys[i]), "'):") + fmt.Printf("%s Edge('%c' | %d):\t", indent, in.keys[i], in.keys[i]) PrintTree(in.children[i], level+1) } } diff --git a/internal/art/util.go b/internal/art/util.go index 5e48f23..0b25d09 100644 --- a/internal/art/util.go +++ b/internal/art/util.go @@ -16,7 +16,7 @@ func addchild(n *Node, k byte, child *Node) { func checkprefix(n *Node, key []byte, depth int) int { in := n.innerNode var i int - for i = 0; i < in.meta.prefixlen && in.meta.prefix[i] == key[depth+i]; i++ { //checks prefix until mismatch + for i = 0; i < in.meta.prefixlen && in.meta.prefix[i] == keycheck(key, depth+i); i++ { //checks prefix until mismatch } return i @@ -33,3 +33,12 @@ func findchild(k byte, n *Node) (*Node, int) { return nil, -1 } + +func keycheck(key []byte, depth int) byte { + if depth >= len(key) { + return 0 + + } else { + return key[depth] + } +} From db64be61b58537d65a841804621b42ad96effda8 Mon Sep 17 00:00:00 2001 From: angelo Date: Fri, 20 Mar 2026 21:01:34 +0530 Subject: [PATCH 05/26] feat:added sorting to addchild --- cmd/radFS/arttest/main.go | 4 +-- internal/art/art.go | 71 +++++++++++++++++++-------------------- internal/art/util.go | 18 ++++++---- 3 files changed, 48 insertions(+), 45 deletions(-) diff --git a/cmd/radFS/arttest/main.go b/cmd/radFS/arttest/main.go index 980c29b..1be876b 100644 --- a/cmd/radFS/arttest/main.go +++ b/cmd/radFS/arttest/main.go @@ -7,8 +7,8 @@ import ( func main() { var t art.Tree - t.Insert([]byte("cat"), "v1") - t.Insert([]byte("car"), "v2") + t.Insert([]byte("cats"), "v1") + t.Insert([]byte("cat"), "v2") t.Insert([]byte("cab"), "v3") v, ok := t.Search([]byte("cat")) println("cat:", v, ok) diff --git a/internal/art/art.go b/internal/art/art.go index 30ac752..aec73cb 100644 --- a/internal/art/art.go +++ b/internal/art/art.go @@ -21,43 +21,6 @@ func (t *Tree) Search(key []byte) (string, bool) { } return "", false } - -func search(n *Node, key []byte, depth int) *Node { - if n == nil { - return nil - } - - if isleaf(n) { - // Verify if the leaf's key actually matches our search key - if string(n.leaf.key) == string(key) { - return n - } - return nil - } - - // 1. Check if the node's prefix matches the current part of the key - if n.innerNode.meta.prefixlen > 0 { - p := checkprefix(n, key, depth) - if p != n.innerNode.meta.prefixlen { - return nil - } - depth += n.innerNode.meta.prefixlen - } - - // 2. Bound check: if we've consumed the prefix but the key is finished, and we aren't at a leaf, the key doesn't exist. - if depth >= len(key) { - return nil - } - - // 3. Find the child corresponding to the byte at the current depth - next, _ := findchild(key[depth], n) - if next != nil { - return search(next, key, depth+1) - } - - return nil -} - func insert(n *Node, value string, key []byte, depth int) *Node { if n == nil { @@ -107,3 +70,37 @@ func insert(n *Node, value string, key []byte, depth int) *Node { } } + +func search(n *Node, key []byte, depth int) *Node { + if n == nil { + return nil + } + + if isleaf(n) { + // Verify if the leaf's key actually matches our search key + if string(n.leaf.key) == string(key) { + return n + } + return nil + } + + // 1. Check if the node's prefix matches the current part of the key + if n.innerNode.meta.prefixlen > 0 { + p := checkprefix(n, key, depth) + if p != n.innerNode.meta.prefixlen { + return nil + } + depth += n.innerNode.meta.prefixlen + } + + // 2. Bound check: if we've consumed the prefix but the key is finished, and we aren't at a leaf, the key doesn't exist. + k := keycheck(key, depth) + + // 3. Find the child corresponding to the byte at the current depth + next, _ := findchild(k, n) + if next != nil { + return search(next, key, depth+1) + } + + return nil +} diff --git a/internal/art/util.go b/internal/art/util.go index 0b25d09..aefcee2 100644 --- a/internal/art/util.go +++ b/internal/art/util.go @@ -2,16 +2,22 @@ package art // TODO: Helper functions (e.g., prefix matching) func addchild(n *Node, k byte, child *Node) { - for i := 0; i < len(n.innerNode.keys); i++ { - if n.innerNode.children[i] == nil { - n.innerNode.children[i] = child - n.innerNode.keys[i] = k - return + in := n.innerNode + pos := 0 + for pos < len(in.keys) && in.children[pos] != nil { + pos++ - } + } + var i int + for i = pos - 1; i > 0 && in.keys[i] > k; i-- { + in.keys[i+1] = in.keys[i] + in.children[i+1] = in.children[i] } + in.keys[i+1] = k + in.children[i+1] = child + } func checkprefix(n *Node, key []byte, depth int) int { in := n.innerNode From 3f86edc33aecef9579e10d72a304442692461b30 Mon Sep 17 00:00:00 2001 From: angelo Date: Fri, 20 Mar 2026 23:57:45 +0530 Subject: [PATCH 06/26] chore: put insert and search in different files --- cmd/radFS/arttest/main.go | 4 +- internal/art/art.go | 83 ------------------------------------- internal/art/insert.go | 51 +++++++++++++++++++++++ internal/art/insert_test.go | 41 ------------------ internal/art/search.go | 35 ++++++++++++++++ 5 files changed, 88 insertions(+), 126 deletions(-) create mode 100644 internal/art/insert.go delete mode 100644 internal/art/insert_test.go create mode 100644 internal/art/search.go diff --git a/cmd/radFS/arttest/main.go b/cmd/radFS/arttest/main.go index 1be876b..430c74d 100644 --- a/cmd/radFS/arttest/main.go +++ b/cmd/radFS/arttest/main.go @@ -7,8 +7,8 @@ import ( func main() { var t art.Tree - t.Insert([]byte("cats"), "v1") - t.Insert([]byte("cat"), "v2") + t.Insert([]byte("cat"), "v1") + t.Insert([]byte("cats"), "v2") t.Insert([]byte("cab"), "v3") v, ok := t.Search([]byte("cat")) println("cat:", v, ok) diff --git a/internal/art/art.go b/internal/art/art.go index aec73cb..0e1f922 100644 --- a/internal/art/art.go +++ b/internal/art/art.go @@ -21,86 +21,3 @@ func (t *Tree) Search(key []byte) (string, bool) { } return "", false } -func insert(n *Node, value string, key []byte, depth int) *Node { - - if n == nil { - return newleaf(value, key) - } - if isleaf(n) { - new_node := newNode4() - oldkey := n.leaf.key - i := depth - for i < len(oldkey) && i < len(key) && oldkey[i] == key[i] { - new_node.innerNode.meta.prefix[i-depth] = key[i] - i++ - } - - new_node.innerNode.meta.prefixlen = i - depth - depth = i - - addchild(new_node, keycheck(key, depth), newleaf(value, key)) - addchild(new_node, keycheck(oldkey, depth), n) - return new_node - - } - p := checkprefix(n, key, depth) - if p != n.innerNode.meta.prefixlen { - new_node := newNode4() - addchild(new_node, keycheck(key, depth+p), newleaf(value, key)) - addchild(new_node, n.innerNode.meta.prefix[p], n) - new_node.innerNode.meta.prefixlen = p - copy(new_node.innerNode.meta.prefix, n.innerNode.meta.prefix[:p]) - - oldprefixlen := n.innerNode.meta.prefixlen - n.innerNode.meta.prefixlen = n.innerNode.meta.prefixlen - (p + 1) - copy(n.innerNode.meta.prefix, n.innerNode.meta.prefix[p+1:oldprefixlen]) - return new_node - } - - depth += n.innerNode.meta.prefixlen - next, pos := findchild(keycheck(key, depth), n) - if next != nil { - n.innerNode.children[pos] = insert(next, value, key, depth+1) - return n - - } else { - addchild(n, keycheck(key, depth), newleaf(value, key)) - return n - - } - -} - -func search(n *Node, key []byte, depth int) *Node { - if n == nil { - return nil - } - - if isleaf(n) { - // Verify if the leaf's key actually matches our search key - if string(n.leaf.key) == string(key) { - return n - } - return nil - } - - // 1. Check if the node's prefix matches the current part of the key - if n.innerNode.meta.prefixlen > 0 { - p := checkprefix(n, key, depth) - if p != n.innerNode.meta.prefixlen { - return nil - } - depth += n.innerNode.meta.prefixlen - } - - // 2. Bound check: if we've consumed the prefix but the key is finished, and we aren't at a leaf, the key doesn't exist. - k := keycheck(key, depth) - - // 3. Find the child corresponding to the byte at the current depth - next, _ := findchild(k, n) - if next != nil { - return search(next, key, depth+1) - } - - return nil -} diff --git a/internal/art/insert.go b/internal/art/insert.go new file mode 100644 index 0000000..d8a541f --- /dev/null +++ b/internal/art/insert.go @@ -0,0 +1,51 @@ +package art + +func insert(n *Node, value string, key []byte, depth int) *Node { + + if n == nil { + return newleaf(value, key) + } + if isleaf(n) { + new_node := newNode4() + oldkey := n.leaf.key + i := depth + for i < len(oldkey) && i < len(key) && oldkey[i] == key[i] { + new_node.innerNode.meta.prefix[i-depth] = key[i] + i++ + } + + new_node.innerNode.meta.prefixlen = i - depth + depth = i + + addchild(new_node, keycheck(key, depth), newleaf(value, key)) + addchild(new_node, keycheck(oldkey, depth), n) + return new_node + + } + p := checkprefix(n, key, depth) + if p != n.innerNode.meta.prefixlen { + new_node := newNode4() + addchild(new_node, keycheck(key, depth+p), newleaf(value, key)) + addchild(new_node, n.innerNode.meta.prefix[p], n) + new_node.innerNode.meta.prefixlen = p + copy(new_node.innerNode.meta.prefix, n.innerNode.meta.prefix[:p]) + + oldprefixlen := n.innerNode.meta.prefixlen + n.innerNode.meta.prefixlen = n.innerNode.meta.prefixlen - (p + 1) + copy(n.innerNode.meta.prefix, n.innerNode.meta.prefix[p+1:oldprefixlen]) + return new_node + } + + depth += n.innerNode.meta.prefixlen + next, pos := findchild(keycheck(key, depth), n) + if next != nil { + n.innerNode.children[pos] = insert(next, value, key, depth+1) + return n + + } else { + addchild(n, keycheck(key, depth), newleaf(value, key)) + return n + + } + +} diff --git a/internal/art/insert_test.go b/internal/art/insert_test.go deleted file mode 100644 index c3e9932..0000000 --- a/internal/art/insert_test.go +++ /dev/null @@ -1,41 +0,0 @@ -package art - -import "testing" - -func TestInsertStructure(t *testing.T) { - var tree Tree - - tree.Insert([]byte("cat"), "v1") - tree.Insert([]byte("car"), "v2") - tree.Insert([]byte("cap"), "v3") - - root := tree.root - - if root == nil { - t.Fatal("root nil") - } - - if isleaf(root) { - t.Fatal("root should not be leaf") - } - - in := root.innerNode - - // check prefix - prefix := string(in.meta.prefix[:in.meta.prefixlen]) - if prefix != "ca" { - t.Fatalf("expected prefix 'ca', got '%s'", prefix) - } - - // check children count - count := 0 - for _, c := range in.children { - if c != nil { - count++ - } - } - - if count != 3 { - t.Fatalf("expected 3 children, got %d", count) - } -} diff --git a/internal/art/search.go b/internal/art/search.go new file mode 100644 index 0000000..631dab2 --- /dev/null +++ b/internal/art/search.go @@ -0,0 +1,35 @@ +package art + +func search(n *Node, key []byte, depth int) *Node { + if n == nil { + return nil + } + + if isleaf(n) { + // Verify if the leaf's key actually matches our search key + if string(n.leaf.key) == string(key) { + return n + } + return nil + } + + // 1. Check if the node's prefix matches the current part of the key + if n.innerNode.meta.prefixlen > 0 { + p := checkprefix(n, key, depth) + if p != n.innerNode.meta.prefixlen { + return nil + } + depth += n.innerNode.meta.prefixlen + } + + // 2. Bound check: if we've consumed the prefix but the key is finished, and we aren't at a leaf, the key doesn't exist. + k := keycheck(key, depth) + + // 3. Find the child corresponding to the byte at the current depth + next, _ := findchild(k, n) + if next != nil { + return search(next, key, depth+1) + } + + return nil +} From 72a7ead6378c5abcaa5e5c3a0f3270da01216c84 Mon Sep 17 00:00:00 2001 From: BhuvignaReddy A T Date: Fri, 20 Mar 2026 22:52:31 +0530 Subject: [PATCH 07/26] Chore:Delete insert_test.go and added comments for search function --- internal/art/art.go | 29 +++++++++++++++----------- internal/art/insert_test.go | 41 ------------------------------------- internal/art/leaf.go | 6 +----- 3 files changed, 18 insertions(+), 58 deletions(-) delete mode 100644 internal/art/insert_test.go diff --git a/internal/art/art.go b/internal/art/art.go index aec73cb..a3c8232 100644 --- a/internal/art/art.go +++ b/internal/art/art.go @@ -13,14 +13,6 @@ func (t *Tree) Root() *Node { func (t *Tree) Insert(key []byte, value string) { t.root = insert(t.root, value, key, 0) } - -func (t *Tree) Search(key []byte) (string, bool) { - leaf := search(t.root, key, 0) // start from root and depth 0 - if leaf != nil && isleaf(leaf) { - return leaf.leaf.values, true //Node->innerleaf->values - } - return "", false -} func insert(n *Node, value string, key []byte, depth int) *Node { if n == nil { @@ -70,21 +62,32 @@ func insert(n *Node, value string, key []byte, depth int) *Node { } } +func (t *Tree) Search(key []byte) (string, bool) { + leaf := search(t.root, key, 0) // start from root and depth 0 + if leaf != nil && isleaf(leaf) { + return leaf.leaf.values, true //Node->innerleaf->values + } + return "", false +} func search(n *Node, key []byte, depth int) *Node { + // Base case: nil node means we've reached a dead end. + // Key does not exist in this path of the tree. if n == nil { return nil } + // Reached a leaf node, do a full key comparison. + // Necessary because path compression may have skipped bytes. if isleaf(n) { - // Verify if the leaf's key actually matches our search key if string(n.leaf.key) == string(key) { return n } return nil } - // 1. Check if the node's prefix matches the current part of the key + // Check if the compressed prefix at this node matches the search key. + // If any byte mismatches, the entire subtree is irrelevant. if n.innerNode.meta.prefixlen > 0 { p := checkprefix(n, key, depth) if p != n.innerNode.meta.prefixlen { @@ -93,10 +96,12 @@ func search(n *Node, key []byte, depth int) *Node { depth += n.innerNode.meta.prefixlen } - // 2. Bound check: if we've consumed the prefix but the key is finished, and we aren't at a leaf, the key doesn't exist. + // Get the next byte to branch on at current depth. + // Returns 0 (terminator) if key is exhausted. k := keycheck(key, depth) - // 3. Find the child corresponding to the byte at the current depth + // Find the child corresponding to byte k and recurse deeper. + // Return nil if no child exists for this byte. next, _ := findchild(k, n) if next != nil { return search(next, key, depth+1) diff --git a/internal/art/insert_test.go b/internal/art/insert_test.go deleted file mode 100644 index c3e9932..0000000 --- a/internal/art/insert_test.go +++ /dev/null @@ -1,41 +0,0 @@ -package art - -import "testing" - -func TestInsertStructure(t *testing.T) { - var tree Tree - - tree.Insert([]byte("cat"), "v1") - tree.Insert([]byte("car"), "v2") - tree.Insert([]byte("cap"), "v3") - - root := tree.root - - if root == nil { - t.Fatal("root nil") - } - - if isleaf(root) { - t.Fatal("root should not be leaf") - } - - in := root.innerNode - - // check prefix - prefix := string(in.meta.prefix[:in.meta.prefixlen]) - if prefix != "ca" { - t.Fatalf("expected prefix 'ca', got '%s'", prefix) - } - - // check children count - count := 0 - for _, c := range in.children { - if c != nil { - count++ - } - } - - if count != 3 { - t.Fatalf("expected 3 children, got %d", count) - } -} diff --git a/internal/art/leaf.go b/internal/art/leaf.go index 48c1a62..87074f1 100644 --- a/internal/art/leaf.go +++ b/internal/art/leaf.go @@ -14,9 +14,5 @@ func newleaf(value string, key []byte) *Node { } func isleaf(n *Node) bool { - if n.leaf != nil { - return true - } else { - return false - } + return n.leaf != nil } From f1c491b0af468ba62090683aa65a8aa1a0a2175d Mon Sep 17 00:00:00 2001 From: angelo Date: Sat, 21 Mar 2026 10:29:24 +0530 Subject: [PATCH 08/26] feat: now can update values and fixed duplicate key bug in addchild --- cmd/radFS/arttest/main.go | 4 ++-- internal/art/print_tree.go | 1 + internal/art/util.go | 12 ++++++++++-- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/cmd/radFS/arttest/main.go b/cmd/radFS/arttest/main.go index 430c74d..0041568 100644 --- a/cmd/radFS/arttest/main.go +++ b/cmd/radFS/arttest/main.go @@ -7,8 +7,8 @@ import ( func main() { var t art.Tree - t.Insert([]byte("cat"), "v1") - t.Insert([]byte("cats"), "v2") + t.Insert([]byte("cart"), "v1") + t.Insert([]byte("car"), "v2") t.Insert([]byte("cab"), "v3") v, ok := t.Search([]byte("cat")) println("cat:", v, ok) diff --git a/internal/art/print_tree.go b/internal/art/print_tree.go index ea36b6d..758150b 100644 --- a/internal/art/print_tree.go +++ b/internal/art/print_tree.go @@ -32,6 +32,7 @@ func PrintTree(n *Node, level int) { for i := 0; i < len(in.keys); i++ { if in.children[i] != nil { fmt.Printf("%s Edge('%c' | %d):\t", indent, in.keys[i], in.keys[i]) + PrintTree(in.children[i], level+1) } } diff --git a/internal/art/util.go b/internal/art/util.go index aefcee2..5b453e0 100644 --- a/internal/art/util.go +++ b/internal/art/util.go @@ -4,12 +4,20 @@ package art func addchild(n *Node, k byte, child *Node) { in := n.innerNode pos := 0 + + child1, pos1 := findchild(k, n) + if child1 != nil { + in.children[pos1] = child + return + } + for pos < len(in.keys) && in.children[pos] != nil { pos++ } + var i int - for i = pos - 1; i > 0 && in.keys[i] > k; i-- { + for i = pos - 1; i >= 0 && in.keys[i] > k; i-- { in.keys[i+1] = in.keys[i] in.children[i+1] = in.children[i] @@ -42,7 +50,7 @@ func findchild(k byte, n *Node) (*Node, int) { func keycheck(key []byte, depth int) byte { if depth >= len(key) { - return 0 + return 1 } else { return key[depth] From dfd6c144fb3c7cec8167401dc0b01240b6fe1c0b Mon Sep 17 00:00:00 2001 From: angelo Date: Tue, 24 Mar 2026 21:20:54 +0530 Subject: [PATCH 09/26] feature: added grow, updated findchild , added num_children feild for each node type --- cmd/radFS/arttest/main.go | 26 +++++++----- internal/art/insert.go | 10 ++--- internal/art/node.go | 15 ++++--- internal/art/node16.go | 12 ++++++ internal/art/node256.go | 13 ++++++ internal/art/node4.go | 7 +-- internal/art/node48.go | 12 ++++++ internal/art/util.go | 89 +++++++++++++++++++++++++++++++++++---- 8 files changed, 151 insertions(+), 33 deletions(-) diff --git a/cmd/radFS/arttest/main.go b/cmd/radFS/arttest/main.go index 0041568..7747edd 100644 --- a/cmd/radFS/arttest/main.go +++ b/cmd/radFS/arttest/main.go @@ -1,23 +1,27 @@ package main import ( + "fmt" + "github.com/acmpesuecc/radFS/internal/art" ) func main() { - var t art.Tree + tree := &art.Tree{} + + tree.Insert([]byte{0x0A}, "first") + tree.Insert([]byte{0x0A, 0x01}, "second") - t.Insert([]byte("cart"), "v1") - t.Insert([]byte("car"), "v2") - t.Insert([]byte("cab"), "v3") - v, ok := t.Search([]byte("cat")) - println("cat:", v, ok) + val, found := tree.Search([]byte{0x0A}) + if !found || val != "first" { - v, ok = t.Search([]byte("cab")) - println("cab:", v, ok) + fmt.Printf("Expected 'first', got %s\n", val) + } - v, ok = t.Search([]byte("cart")) - println("cart:", v, ok) + val2, found2 := tree.Search([]byte{0x0A, 0x01}) + if !found2 || val2 != "second" { + fmt.Printf("Expected 'second', got %s\n", val2) + } - art.PrintTree(t.Root(), 0) + //art.PrintTree(tree.Root(), 0) } diff --git a/internal/art/insert.go b/internal/art/insert.go index d8a541f..b606a1a 100644 --- a/internal/art/insert.go +++ b/internal/art/insert.go @@ -17,16 +17,16 @@ func insert(n *Node, value string, key []byte, depth int) *Node { new_node.innerNode.meta.prefixlen = i - depth depth = i - addchild(new_node, keycheck(key, depth), newleaf(value, key)) - addchild(new_node, keycheck(oldkey, depth), n) + new_node = addchild(new_node, keycheck(key, depth), newleaf(value, key)) + new_node = addchild(new_node, keycheck(oldkey, depth), n) return new_node } p := checkprefix(n, key, depth) if p != n.innerNode.meta.prefixlen { new_node := newNode4() - addchild(new_node, keycheck(key, depth+p), newleaf(value, key)) - addchild(new_node, n.innerNode.meta.prefix[p], n) + new_node = addchild(new_node, keycheck(key, depth+p), newleaf(value, key)) + new_node = addchild(new_node, n.innerNode.meta.prefix[p], n) new_node.innerNode.meta.prefixlen = p copy(new_node.innerNode.meta.prefix, n.innerNode.meta.prefix[:p]) @@ -43,7 +43,7 @@ func insert(n *Node, value string, key []byte, depth int) *Node { return n } else { - addchild(n, keycheck(key, depth), newleaf(value, key)) + n = addchild(n, keycheck(key, depth), newleaf(value, key)) return n } diff --git a/internal/art/node.go b/internal/art/node.go index 99f720d..e41ecd5 100644 --- a/internal/art/node.go +++ b/internal/art/node.go @@ -11,7 +11,11 @@ const ( Node256 ) const ( - Node4max = 4 + Node4max = 4 + Node16Max = 16 + Node48Max = 48 + Node256Max = 256 + maxprefixlen = 8 ) @@ -21,10 +25,11 @@ type Node struct { } type innerNode struct { - nodeType NodeType - keys []byte - children []*Node - meta meta + nodeType NodeType + keys []byte + children []*Node + num_children int + meta meta } type meta struct { diff --git a/internal/art/node16.go b/internal/art/node16.go index 85b049d..a931480 100644 --- a/internal/art/node16.go +++ b/internal/art/node16.go @@ -1,3 +1,15 @@ package art // TODO: Node16 implementation +func newNode16() *Node { + in := &innerNode{ + nodeType: Node16, + keys: make([]byte, Node16Max), + children: make([]*Node, Node16Max), + num_children: 0, + meta: meta{ + prefix: make([]byte, maxprefixlen), + }, + } + return &Node{innerNode: in} +} diff --git a/internal/art/node256.go b/internal/art/node256.go index 7079796..0a891fc 100644 --- a/internal/art/node256.go +++ b/internal/art/node256.go @@ -1,3 +1,16 @@ package art // TODO: Node256 implementation (direct map) + +func newNode256() *Node { + in := &innerNode{ + nodeType: Node256, + children: make([]*Node, Node256Max), + num_children: 0, + + meta: meta{ + prefix: make([]byte, maxprefixlen), + }, + } + return &Node{innerNode: in} +} diff --git a/internal/art/node4.go b/internal/art/node4.go index 1e154c2..aa996b8 100644 --- a/internal/art/node4.go +++ b/internal/art/node4.go @@ -2,9 +2,10 @@ package art func newNode4() *Node { in := &innerNode{ - nodeType: Node4, - keys: make([]byte, Node4max), - children: make([]*Node, Node4max), + nodeType: Node4, + keys: make([]byte, Node4max), + children: make([]*Node, Node4max), + num_children: 0, meta: meta{ prefix: make([]byte, maxprefixlen), }, diff --git a/internal/art/node48.go b/internal/art/node48.go index 43de596..f99a5ca 100644 --- a/internal/art/node48.go +++ b/internal/art/node48.go @@ -1,3 +1,15 @@ package art // TODO: Node48 implementation (indirection layer) +func newNode48() *Node { + in := &innerNode{ + nodeType: Node48, + keys: make([]byte, Node256Max), + children: make([]*Node, Node48Max), + num_children: 0, + meta: meta{ + prefix: make([]byte, maxprefixlen), + }, + } + return &Node{innerNode: in} +} diff --git a/internal/art/util.go b/internal/art/util.go index 5b453e0..3399b15 100644 --- a/internal/art/util.go +++ b/internal/art/util.go @@ -1,23 +1,23 @@ package art // TODO: Helper functions (e.g., prefix matching) -func addchild(n *Node, k byte, child *Node) { +func addchild(n *Node, k byte, child *Node) *Node { in := n.innerNode - pos := 0 child1, pos1 := findchild(k, n) if child1 != nil { in.children[pos1] = child - return + return n } - for pos < len(in.keys) && in.children[pos] != nil { - pos++ + if n.innerNode.num_children == len(in.keys) { + n = grow(n) + in = n.innerNode } var i int - for i = pos - 1; i >= 0 && in.keys[i] > k; i-- { + for i = in.num_children - 1; i >= 0 && in.keys[i] > k; i-- { in.keys[i+1] = in.keys[i] in.children[i+1] = in.children[i] @@ -25,6 +25,9 @@ func addchild(n *Node, k byte, child *Node) { in.keys[i+1] = k in.children[i+1] = child + in.num_children += 1 + + return n } func checkprefix(n *Node, key []byte, depth int) int { @@ -38,9 +41,24 @@ func checkprefix(n *Node, key []byte, depth int) int { } func findchild(k byte, n *Node) (*Node, int) { in := n.innerNode - for i := 0; i < len(in.keys); i++ { - if in.keys[i] == k { - return in.children[i], i //finds the node and the position + switch in.nodeType { + case Node4, Node16: + for i := 0; i < len(in.keys); i++ { + if in.keys[i] == k { + return in.children[i], i //finds the node and the position + } + + } + case Node48: + idx := in.keys[k] + if idx > 0 { + realindex := int(idx - 1) + return in.children[realindex], realindex + + } + case Node256: + if in.children[k] != nil { + return in.children[k], int(k) } } @@ -56,3 +74,56 @@ func keycheck(key []byte, depth int) byte { return key[depth] } } + +func grow(n *Node) *Node { + switch n.innerNode.nodeType { + case Node4: + n16 := newNode16() + copymeta(n, n16) + for i := 0; i < 4; i++ { + n16.innerNode.keys[i] = n.innerNode.keys[i] + n16.innerNode.children[i] = n.innerNode.children[i] + } + return n16 + case Node16: + n48 := newNode48() + copymeta(n, n48) + index := 0 + for i := 0; i < 16; i++ { + idx := n.innerNode.keys[i] + child := n.innerNode.children[i] + + if child != nil { + n48.innerNode.keys[idx] = byte(index + 1) // the reason its index+1 is because we are making 0 a kind of "no children" case + + n48.innerNode.children[index] = child + index++ + + } + + } + return n48 + + case Node48: + n256 := newNode256() + copymeta(n, n256) + for i := 0; i < 256; i++ { + idx := n.innerNode.keys[i] + + if n.innerNode.keys[i] != 0 { + child := n.innerNode.children[int(idx-1)] + n256.innerNode.children[i] = child + } + + } + return n256 + + } + return nil + +} +func copymeta(n *Node, new_node *Node) { + new_node.innerNode.meta.prefix = n.innerNode.meta.prefix + new_node.innerNode.meta.prefixlen = n.innerNode.meta.prefixlen + +} From 56761fd9fc75d29101c26003368caec5a0864c78 Mon Sep 17 00:00:00 2001 From: BhuvignaReddy A T Date: Tue, 24 Mar 2026 21:46:12 +0530 Subject: [PATCH 10/26] feat: add delete support-recursive deletekey and removechild utility func --- cmd/radFS/arttest/main.go | 10 +++++++--- internal/art/art.go | 8 ++++++++ internal/art/delete.go | 41 +++++++++++++++++++++++++++++++++++++++ internal/art/util.go | 36 +++++++++++++++++++++++----------- 4 files changed, 81 insertions(+), 14 deletions(-) create mode 100644 internal/art/delete.go diff --git a/cmd/radFS/arttest/main.go b/cmd/radFS/arttest/main.go index 0041568..7303a21 100644 --- a/cmd/radFS/arttest/main.go +++ b/cmd/radFS/arttest/main.go @@ -10,14 +10,18 @@ func main() { t.Insert([]byte("cart"), "v1") t.Insert([]byte("car"), "v2") t.Insert([]byte("cab"), "v3") + println("===Tree before deletion===") + art.PrintTree(t.Root(), 0) + t.Delete([]byte("car")) + println("===Tree after deletion of 'car'===") + art.PrintTree(t.Root(), 0) v, ok := t.Search([]byte("cat")) println("cat:", v, ok) - v, ok = t.Search([]byte("cab")) - println("cab:", v, ok) + v, ok = t.Search([]byte("car")) + println("car:", v, ok) v, ok = t.Search([]byte("cart")) println("cart:", v, ok) - art.PrintTree(t.Root(), 0) } diff --git a/internal/art/art.go b/internal/art/art.go index 0e1f922..ff90989 100644 --- a/internal/art/art.go +++ b/internal/art/art.go @@ -21,3 +21,11 @@ func (t *Tree) Search(key []byte) (string, bool) { } return "", false } + +func (t *Tree) Delete(key []byte) bool { + newRoot, deleted := deletekey(t.root, key, 0) + if deleted { + t.root = newRoot + } + return deleted +} diff --git a/internal/art/delete.go b/internal/art/delete.go new file mode 100644 index 0000000..026ead8 --- /dev/null +++ b/internal/art/delete.go @@ -0,0 +1,41 @@ +package art + +// deletekey recursively walks the tree to find and remove the given key. +// Returns the (possibly modified) node and whether the key was deleted. +func deletekey(n *Node, key []byte, depth int) (*Node, bool) { + if n == nil { + return nil, false + } + + if isleaf(n) { + if string(n.leaf.key) == string(key) { + return nil, true + } + return n, false + } + + p := checkprefix(n, key, depth) + if p != n.innerNode.meta.prefixlen { + return n, false + } + depth += n.innerNode.meta.prefixlen + + k := keycheck(key, depth) + child, pos := findchild(k, n) + if child == nil { + return n, false + } + + newChild, deleted := deletekey(child, key, depth+1) + if !deleted { + return n, false + } + + if newChild == nil { + removechild(n, k) + } else { + n.innerNode.children[pos] = newChild + } + + return n, true +} diff --git a/internal/art/util.go b/internal/art/util.go index 5b453e0..b20d21c 100644 --- a/internal/art/util.go +++ b/internal/art/util.go @@ -4,20 +4,12 @@ package art func addchild(n *Node, k byte, child *Node) { in := n.innerNode pos := 0 - - child1, pos1 := findchild(k, n) - if child1 != nil { - in.children[pos1] = child - return - } - for pos < len(in.keys) && in.children[pos] != nil { pos++ } - var i int - for i = pos - 1; i >= 0 && in.keys[i] > k; i-- { + for i = pos - 1; i > 0 && in.keys[i] > k; i-- { in.keys[i+1] = in.keys[i] in.children[i+1] = in.children[i] @@ -36,6 +28,7 @@ func checkprefix(n *Node, key []byte, depth int) int { return i } + func findchild(k byte, n *Node) (*Node, int) { in := n.innerNode for i := 0; i < len(in.keys); i++ { @@ -47,12 +40,33 @@ func findchild(k byte, n *Node) (*Node, int) { return nil, -1 } - func keycheck(key []byte, depth int) byte { if depth >= len(key) { - return 1 + return 0 } else { return key[depth] } } + +// removechild removes the child with key k from node n by shifting +// all subsequent keys and children left to fill the gap. +func removechild(n *Node, k byte) { + _, pos := findchild(k, n) + if pos == -1 { + return // key not found, nothing to remove + } + + in := n.innerNode + last := len(in.keys) - 1 + + // shift everything after pos one step to the left + for i := pos; i < last; i++ { + in.keys[i] = in.keys[i+1] + in.children[i] = in.children[i+1] + } + + // clear the now-duplicate last slot to avoid stale pointers + in.keys[last] = 0 + in.children[last] = nil +} From 0352d08db8092b4443201b47b753953b5190bb5b Mon Sep 17 00:00:00 2001 From: angelo Date: Fri, 27 Mar 2026 20:14:30 +0530 Subject: [PATCH 11/26] chore :the tree now handles the case when prefix is longer than maxprefixlen --- cmd/radFS/arttest/main.go | 20 ++++------------- internal/art/insert.go | 45 +++++++++++++++++++++++++++++++++----- internal/art/print_tree.go | 11 ++++++---- internal/art/util.go | 34 ++++++++++++++++++++++++++-- 4 files changed, 83 insertions(+), 27 deletions(-) diff --git a/cmd/radFS/arttest/main.go b/cmd/radFS/arttest/main.go index 7747edd..6388106 100644 --- a/cmd/radFS/arttest/main.go +++ b/cmd/radFS/arttest/main.go @@ -1,27 +1,15 @@ package main import ( - "fmt" - "github.com/acmpesuecc/radFS/internal/art" ) func main() { tree := &art.Tree{} - tree.Insert([]byte{0x0A}, "first") - tree.Insert([]byte{0x0A, 0x01}, "second") - - val, found := tree.Search([]byte{0x0A}) - if !found || val != "first" { - - fmt.Printf("Expected 'first', got %s\n", val) - } - - val2, found2 := tree.Search([]byte{0x0A, 0x01}) - if !found2 || val2 != "second" { - fmt.Printf("Expected 'second', got %s\n", val2) - } + tree.Insert([]byte("abbbbbbbc"), "first") + tree.Insert([]byte("abbbbbbbcb"), "second") + tree.Insert([]byte("abbbbbbbcbc"), "second") - //art.PrintTree(tree.Root(), 0) + art.PrintTree(tree.Root(), 0) } diff --git a/internal/art/insert.go b/internal/art/insert.go index b606a1a..1839d78 100644 --- a/internal/art/insert.go +++ b/internal/art/insert.go @@ -1,5 +1,7 @@ package art +import "fmt" + func insert(n *Node, value string, key []byte, depth int) *Node { if n == nil { @@ -9,16 +11,31 @@ func insert(n *Node, value string, key []byte, depth int) *Node { new_node := newNode4() oldkey := n.leaf.key i := depth + fmt.Println("depth:", depth) + fmt.Println("oldkey[depth:]:", string(oldkey[depth:])) + fmt.Println("key[depth:]:", string(key[depth:])) + for i < len(oldkey) && i < len(key) && oldkey[i] == key[i] { - new_node.innerNode.meta.prefix[i-depth] = key[i] + prefix_index := i - depth + if prefix_index < maxprefixlen { //index goes till 7 so prefix index<8 and not ==8 + new_node.innerNode.meta.prefix[prefix_index] = key[i] // stores only the till max prefix + + } + i++ } - new_node.innerNode.meta.prefixlen = i - depth + new_node.innerNode.meta.prefixlen = i - depth // stores full prefix len even after maxprefixlen depth = i new_node = addchild(new_node, keycheck(key, depth), newleaf(value, key)) + new_node = addchild(new_node, keycheck(oldkey, depth), n) + fmt.Println("depth:", depth) + fmt.Println("oldkey[depth:]:", string(oldkey[depth:])) + fmt.Println("key[depth:]:", string(key[depth:])) + fmt.Println(" ") + fmt.Println("prefix", string(new_node.innerNode.meta.prefix)) return new_node } @@ -26,13 +43,31 @@ func insert(n *Node, value string, key []byte, depth int) *Node { if p != n.innerNode.meta.prefixlen { new_node := newNode4() new_node = addchild(new_node, keycheck(key, depth+p), newleaf(value, key)) - new_node = addchild(new_node, n.innerNode.meta.prefix[p], n) + if p < maxprefixlen { + new_node = addchild(new_node, n.innerNode.meta.prefix[p], n) + + } else { + leaf := fetchleaf(n) + new_node = addchild(new_node, keycheck(leaf.leaf.key, depth+p), n) // the logic is teh leaf will contain the full key with the same prefix + } + new_node.innerNode.meta.prefixlen = p - copy(new_node.innerNode.meta.prefix, n.innerNode.meta.prefix[:p]) + if p < maxprefixlen { + copy(new_node.innerNode.meta.prefix, n.innerNode.meta.prefix[:p]) + + } else { + copy(new_node.innerNode.meta.prefix, n.innerNode.meta.prefix[:maxprefixlen]) + } oldprefixlen := n.innerNode.meta.prefixlen n.innerNode.meta.prefixlen = n.innerNode.meta.prefixlen - (p + 1) - copy(n.innerNode.meta.prefix, n.innerNode.meta.prefix[p+1:oldprefixlen]) + if len(n.innerNode.meta.prefix[p+1:oldprefixlen]) < maxprefixlen { + copy(n.innerNode.meta.prefix, n.innerNode.meta.prefix[p+1:oldprefixlen]) + + } else { + copy(n.innerNode.meta.prefix, n.innerNode.meta.prefix[p+1:maxprefixlen]) + } + return new_node } diff --git a/internal/art/print_tree.go b/internal/art/print_tree.go index 758150b..ccb9b3f 100644 --- a/internal/art/print_tree.go +++ b/internal/art/print_tree.go @@ -20,11 +20,14 @@ func PrintTree(n *Node, level int) { in := n.innerNode prefixlen := in.meta.prefixlen - if prefixlen < 0 || prefixlen > len(in.meta.prefix) { - prefixlen = 0 - } + prefix := "" + if prefixlen < maxprefixlen { + prefix = string(in.meta.prefix[:prefixlen]) - prefix := string(in.meta.prefix[:prefixlen]) + } else { + leaf := fetchleaf(n) + prefix = string(leaf.leaf.key) + } fmt.Println(indent+"Node(prefix=\""+prefix+"\", prefixLen=", prefixlen, ")") diff --git a/internal/art/util.go b/internal/art/util.go index 3399b15..6ade844 100644 --- a/internal/art/util.go +++ b/internal/art/util.go @@ -33,10 +33,25 @@ func addchild(n *Node, k byte, child *Node) *Node { func checkprefix(n *Node, key []byte, depth int) int { in := n.innerNode var i int - for i = 0; i < in.meta.prefixlen && in.meta.prefix[i] == keycheck(key, depth+i); i++ { //checks prefix until mismatch + maxcmp := min(maxprefixlen, in.meta.prefixlen) + + for i = 0; i < maxcmp; i++ { //checks prefix until mismatch + if in.meta.prefix[i] != keycheck(key, depth+i) { + return i // case when you find mismatch and the mismatch is less than maxprefixlen + + } } - return i + if in.meta.prefixlen > maxprefixlen { + leaf := fetchleaf(n) + for ; i < in.meta.prefixlen && keycheck(key, depth+i) == keycheck(leaf.leaf.key, depth+i); i++ { + + } + return i // case when you find mismatch and the mismatch is more than maxprefixlen + + } + + return i // case when you find mismatch and the mismatch is equal maxprefixlen } func findchild(k byte, n *Node) (*Node, int) { @@ -127,3 +142,18 @@ func copymeta(n *Node, new_node *Node) { new_node.innerNode.meta.prefixlen = n.innerNode.meta.prefixlen } + +func fetchleaf(n *Node) *Node { + if isleaf(n) { + return n + } + for i := 0; i < len(n.innerNode.keys); i++ { + if n.innerNode.children[i] != nil { + return fetchleaf(n.innerNode.children[i]) + + } + + } + return nil + +} From 278149a5661f09574e4f5a0bb0e9aef0e077090d Mon Sep 17 00:00:00 2001 From: angelo Date: Sat, 28 Mar 2026 18:59:17 +0530 Subject: [PATCH 12/26] feat: removed the use of sentinal value and stores the leaf inside innernode --- cmd/radFS/arttest/main.go | 12 +++++--- internal/art/insert.go | 59 +++++++++++++++++++++++++------------- internal/art/node.go | 11 ++++--- internal/art/print_tree.go | 13 ++++++--- internal/art/search.go | 2 +- internal/art/util.go | 40 +++++++++++++++----------- 6 files changed, 88 insertions(+), 49 deletions(-) diff --git a/cmd/radFS/arttest/main.go b/cmd/radFS/arttest/main.go index 6388106..48611cd 100644 --- a/cmd/radFS/arttest/main.go +++ b/cmd/radFS/arttest/main.go @@ -7,9 +7,13 @@ import ( func main() { tree := &art.Tree{} - tree.Insert([]byte("abbbbbbbc"), "first") - tree.Insert([]byte("abbbbbbbcb"), "second") - tree.Insert([]byte("abbbbbbbcbc"), "second") + tree.Insert([]byte("cab"), "first") - art.PrintTree(tree.Root(), 0) + tree.Insert([]byte("can"), "second") + tree.Insert([]byte("car"), "first") + tree.Insert([]byte("cat"), "first") + + tree.Insert([]byte("caz"), "second") + + art.PrintTree(tree.Root(), 0, 0) } diff --git a/internal/art/insert.go b/internal/art/insert.go index 1839d78..7064556 100644 --- a/internal/art/insert.go +++ b/internal/art/insert.go @@ -1,7 +1,5 @@ package art -import "fmt" - func insert(n *Node, value string, key []byte, depth int) *Node { if n == nil { @@ -11,9 +9,6 @@ func insert(n *Node, value string, key []byte, depth int) *Node { new_node := newNode4() oldkey := n.leaf.key i := depth - fmt.Println("depth:", depth) - fmt.Println("oldkey[depth:]:", string(oldkey[depth:])) - fmt.Println("key[depth:]:", string(key[depth:])) for i < len(oldkey) && i < len(key) && oldkey[i] == key[i] { prefix_index := i - depth @@ -27,28 +22,51 @@ func insert(n *Node, value string, key []byte, depth int) *Node { new_node.innerNode.meta.prefixlen = i - depth // stores full prefix len even after maxprefixlen depth = i + if depth == len(key) { + new_node.innerNode.leaf = newleaf(value, key) + + } else { + new_node = addchild(new_node, key[depth], newleaf(value, key)) + + } + if depth == len(oldkey) { + new_node.innerNode.leaf = n - new_node = addchild(new_node, keycheck(key, depth), newleaf(value, key)) + } else { + new_node = addchild(new_node, oldkey[depth], n) + + } - new_node = addchild(new_node, keycheck(oldkey, depth), n) - fmt.Println("depth:", depth) - fmt.Println("oldkey[depth:]:", string(oldkey[depth:])) - fmt.Println("key[depth:]:", string(key[depth:])) - fmt.Println(" ") - fmt.Println("prefix", string(new_node.innerNode.meta.prefix)) return new_node } p := checkprefix(n, key, depth) + if p != n.innerNode.meta.prefixlen { + new_node := newNode4() - new_node = addchild(new_node, keycheck(key, depth+p), newleaf(value, key)) + if p+depth == len(key) { + new_node.innerNode.leaf = newleaf(value, key) + + } else { + new_node = addchild(new_node, key[depth+p], newleaf(value, key)) + + } + leaf := fetchleaf(n) // either its an actual leaf or innernode leaf + oldkey := leaf.leaf.key + + var oldkeybyte byte if p < maxprefixlen { - new_node = addchild(new_node, n.innerNode.meta.prefix[p], n) + oldkeybyte = n.innerNode.meta.prefix[p] + } else { + oldkeybyte = oldkey[depth+p] + } + + if p+depth == len(oldkey) { //when the split is exactly the prefix ends eg intern was already there and you add internship + new_node.innerNode.leaf = leaf // promote newnode leaf to previous leaf } else { - leaf := fetchleaf(n) - new_node = addchild(new_node, keycheck(leaf.leaf.key, depth+p), n) // the logic is teh leaf will contain the full key with the same prefix + new_node = addchild(new_node, oldkeybyte, n) } new_node.innerNode.meta.prefixlen = p @@ -60,25 +78,26 @@ func insert(n *Node, value string, key []byte, depth int) *Node { } oldprefixlen := n.innerNode.meta.prefixlen - n.innerNode.meta.prefixlen = n.innerNode.meta.prefixlen - (p + 1) + n.innerNode.meta.prefixlen = oldprefixlen - (p + 1) if len(n.innerNode.meta.prefix[p+1:oldprefixlen]) < maxprefixlen { copy(n.innerNode.meta.prefix, n.innerNode.meta.prefix[p+1:oldprefixlen]) } else { - copy(n.innerNode.meta.prefix, n.innerNode.meta.prefix[p+1:maxprefixlen]) + leaf := fetchleaf(n) + copy(n.innerNode.meta.prefix, leaf.leaf.key[depth+p+1:depth+p+1+maxprefixlen]) } return new_node } depth += n.innerNode.meta.prefixlen - next, pos := findchild(keycheck(key, depth), n) + next, pos := findchild(key[depth], n) if next != nil { n.innerNode.children[pos] = insert(next, value, key, depth+1) return n } else { - n = addchild(n, keycheck(key, depth), newleaf(value, key)) + n = addchild(n, key[depth], newleaf(value, key)) return n } diff --git a/internal/art/node.go b/internal/art/node.go index e41ecd5..b381512 100644 --- a/internal/art/node.go +++ b/internal/art/node.go @@ -25,11 +25,14 @@ type Node struct { } type innerNode struct { - nodeType NodeType - keys []byte - children []*Node + nodeType NodeType + keys []byte + children []*Node + leaf *Node + num_children int - meta meta + + meta meta } type meta struct { diff --git a/internal/art/print_tree.go b/internal/art/print_tree.go index ccb9b3f..5a332b6 100644 --- a/internal/art/print_tree.go +++ b/internal/art/print_tree.go @@ -2,7 +2,7 @@ package art import "fmt" -func PrintTree(n *Node, level int) { +func PrintTree(n *Node, level int, depth int) { if n == nil { return } @@ -21,22 +21,27 @@ func PrintTree(n *Node, level int) { prefixlen := in.meta.prefixlen prefix := "" - if prefixlen < maxprefixlen { + if prefixlen <= maxprefixlen { prefix = string(in.meta.prefix[:prefixlen]) } else { leaf := fetchleaf(n) - prefix = string(leaf.leaf.key) + prefix = string(leaf.leaf.key[depth : depth+prefixlen]) } fmt.Println(indent+"Node(prefix=\""+prefix+"\", prefixLen=", prefixlen, ")") + if in.leaf != nil { + fmt.Printf("%s [Internal Leaf]: %s\n", indent, string(in.leaf.leaf.key)) + } // Print children + + newDepth := depth + prefixlen for i := 0; i < len(in.keys); i++ { if in.children[i] != nil { fmt.Printf("%s Edge('%c' | %d):\t", indent, in.keys[i], in.keys[i]) - PrintTree(in.children[i], level+1) + PrintTree(in.children[i], level+1, newDepth+1) } } } diff --git a/internal/art/search.go b/internal/art/search.go index 34472f3..3ca8dd3 100644 --- a/internal/art/search.go +++ b/internal/art/search.go @@ -28,7 +28,7 @@ func search(n *Node, key []byte, depth int) *Node { // Get the next byte to branch on at current depth. // Returns 0 (terminator) if key is exhausted. - k := keycheck(key, depth) + k := key[depth] // Find the child corresponding to byte k and recurse deeper. // Return nil if no child exists for this byte. diff --git a/internal/art/util.go b/internal/art/util.go index 6ade844..dac41d0 100644 --- a/internal/art/util.go +++ b/internal/art/util.go @@ -35,8 +35,8 @@ func checkprefix(n *Node, key []byte, depth int) int { var i int maxcmp := min(maxprefixlen, in.meta.prefixlen) - for i = 0; i < maxcmp; i++ { //checks prefix until mismatch - if in.meta.prefix[i] != keycheck(key, depth+i) { + for i = 0; i < maxcmp && depth+i < len(key); i++ { //checks prefix until mismatch + if in.meta.prefix[i] != key[depth+i] { return i // case when you find mismatch and the mismatch is less than maxprefixlen } @@ -44,10 +44,14 @@ func checkprefix(n *Node, key []byte, depth int) int { } if in.meta.prefixlen > maxprefixlen { leaf := fetchleaf(n) - for ; i < in.meta.prefixlen && keycheck(key, depth+i) == keycheck(leaf.leaf.key, depth+i); i++ { + leafkey := leaf.leaf.key + for ; i < in.meta.prefixlen && depth+i < len(leafkey) && depth+i < len(key); i++ { + if key[depth+i] != leaf.leaf.key[depth+i] { + return i // case when you find mismatch and the mismatch is more than maxprefixlen + + } } - return i // case when you find mismatch and the mismatch is more than maxprefixlen } @@ -58,7 +62,7 @@ func findchild(k byte, n *Node) (*Node, int) { in := n.innerNode switch in.nodeType { case Node4, Node16: - for i := 0; i < len(in.keys); i++ { + for i := 0; i < in.num_children; i++ { if in.keys[i] == k { return in.children[i], i //finds the node and the position } @@ -81,24 +85,22 @@ func findchild(k byte, n *Node) (*Node, int) { } -func keycheck(key []byte, depth int) byte { - if depth >= len(key) { - return 1 - - } else { - return key[depth] - } -} - func grow(n *Node) *Node { switch n.innerNode.nodeType { case Node4: n16 := newNode16() copymeta(n, n16) + index := 0 for i := 0; i < 4; i++ { - n16.innerNode.keys[i] = n.innerNode.keys[i] - n16.innerNode.children[i] = n.innerNode.children[i] + if n.innerNode.children[i] != nil { + n16.innerNode.keys[index] = n.innerNode.keys[i] + n16.innerNode.children[index] = n.innerNode.children[i] + index++ + + } + } + n16.innerNode.num_children = index return n16 case Node16: n48 := newNode48() @@ -117,6 +119,7 @@ func grow(n *Node) *Node { } } + n48.innerNode.num_children = index return n48 case Node48: @@ -131,6 +134,7 @@ func grow(n *Node) *Node { } } + return n256 } @@ -147,6 +151,10 @@ func fetchleaf(n *Node) *Node { if isleaf(n) { return n } + if n.innerNode.leaf != nil { + return n.innerNode.leaf + } + for i := 0; i < len(n.innerNode.keys); i++ { if n.innerNode.children[i] != nil { return fetchleaf(n.innerNode.children[i]) From 34e03895b317d74ae7a2c3d2a50ab9b6c505c4c7 Mon Sep 17 00:00:00 2001 From: angelo Date: Sat, 28 Mar 2026 21:53:12 +0530 Subject: [PATCH 13/26] chore :week4 log --- cmd/radFS/arttest/main.go | 10 ++++------ docs/weekly/angelo/week_4.md | 8 ++++++++ internal/art/insert.go | 9 ++------- 3 files changed, 14 insertions(+), 13 deletions(-) create mode 100644 docs/weekly/angelo/week_4.md diff --git a/cmd/radFS/arttest/main.go b/cmd/radFS/arttest/main.go index 48611cd..be2817b 100644 --- a/cmd/radFS/arttest/main.go +++ b/cmd/radFS/arttest/main.go @@ -7,13 +7,11 @@ import ( func main() { tree := &art.Tree{} - tree.Insert([]byte("cab"), "first") + tree.Insert([]byte("int"), "first") - tree.Insert([]byte("can"), "second") - tree.Insert([]byte("car"), "first") - tree.Insert([]byte("cat"), "first") - - tree.Insert([]byte("caz"), "second") + tree.Insert([]byte("intern"), "second") + tree.Insert([]byte("internet"), "first") + tree.Insert([]byte("i"), "first") art.PrintTree(tree.Root(), 0, 0) } diff --git a/docs/weekly/angelo/week_4.md b/docs/weekly/angelo/week_4.md new file mode 100644 index 0000000..c7119e6 --- /dev/null +++ b/docs/weekly/angelo/week_4.md @@ -0,0 +1,8 @@ +## Angelo's Progress +* implemented the insert fuction +* implemented update function in addchild + + +## What's next? +* implement grow to transistion to larger nodes +* fix the bugs in insert diff --git a/internal/art/insert.go b/internal/art/insert.go index 7064556..0fcd0ad 100644 --- a/internal/art/insert.go +++ b/internal/art/insert.go @@ -12,7 +12,7 @@ func insert(n *Node, value string, key []byte, depth int) *Node { for i < len(oldkey) && i < len(key) && oldkey[i] == key[i] { prefix_index := i - depth - if prefix_index < maxprefixlen { //index goes till 7 so prefix index<8 and not ==8 + if prefix_index < maxprefixlen { new_node.innerNode.meta.prefix[prefix_index] = key[i] // stores only the till max prefix } @@ -62,12 +62,7 @@ func insert(n *Node, value string, key []byte, depth int) *Node { oldkeybyte = oldkey[depth+p] } - if p+depth == len(oldkey) { //when the split is exactly the prefix ends eg intern was already there and you add internship - new_node.innerNode.leaf = leaf // promote newnode leaf to previous leaf - - } else { - new_node = addchild(new_node, oldkeybyte, n) - } + new_node = addchild(new_node, oldkeybyte, n) new_node.innerNode.meta.prefixlen = p if p < maxprefixlen { From d85da6a957bc3a9e4f4e77334b30f6f2eb99116f Mon Sep 17 00:00:00 2001 From: Angelo Arakal Date: Sun, 29 Mar 2026 01:01:59 +0530 Subject: [PATCH 14/26] Update docs/weekly/angelo/week_4.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- docs/weekly/angelo/week_4.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/weekly/angelo/week_4.md b/docs/weekly/angelo/week_4.md index c7119e6..71c7b67 100644 --- a/docs/weekly/angelo/week_4.md +++ b/docs/weekly/angelo/week_4.md @@ -1,5 +1,5 @@ ## Angelo's Progress -* implemented the insert fuction +* implemented the insert function * implemented update function in addchild From 5c3fb51571ca216211752d288f8482dad1275b01 Mon Sep 17 00:00:00 2001 From: Angelo Arakal Date: Sun, 29 Mar 2026 01:02:20 +0530 Subject: [PATCH 15/26] Update docs/weekly/angelo/week_4.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- docs/weekly/angelo/week_4.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/weekly/angelo/week_4.md b/docs/weekly/angelo/week_4.md index 71c7b67..db281f3 100644 --- a/docs/weekly/angelo/week_4.md +++ b/docs/weekly/angelo/week_4.md @@ -4,5 +4,5 @@ ## What's next? -* implement grow to transistion to larger nodes +* implement grow to transition to larger nodes * fix the bugs in insert From 2504374d8c18b2c76a954eaa377f678c578a7fa1 Mon Sep 17 00:00:00 2001 From: BhuvignaReddy A T Date: Mon, 30 Mar 2026 23:13:57 +0530 Subject: [PATCH 16/26] fix(insert): remove sentinel values and fix key exhaustion bug in insert, merged conflicts from angelo --- cmd/radFS/arttest/main.go | 6 ++- docs/weekly/angelo/week_4.md | 8 ++++ internal/art/insert.go | 71 ++++++++++++++++++++++++++++++------ internal/art/node.go | 11 ++++-- internal/art/print_tree.go | 20 +++++++--- internal/art/search.go | 21 +++++------ internal/art/util.go | 65 ++++++++++++++++++++++++++------- 7 files changed, 155 insertions(+), 47 deletions(-) create mode 100644 docs/weekly/angelo/week_4.md diff --git a/cmd/radFS/arttest/main.go b/cmd/radFS/arttest/main.go index 89658b4..3e51742 100644 --- a/cmd/radFS/arttest/main.go +++ b/cmd/radFS/arttest/main.go @@ -7,18 +7,20 @@ import ( func main() { t := &art.Tree{} + t.Insert([]byte("cart"), "v1") t.Insert([]byte("car"), "v2") t.Insert([]byte("cab"), "v3") println("===Tree before deletion===") - art.PrintTree(t.Root(), 0) + art.PrintTree(t.Root(),0 ,0) t.Delete([]byte("car")) println("===Tree after deletion of 'car'===") - art.PrintTree(t.Root(), 0) + art.PrintTree(t.Root(), 0, 0) v, ok := t.Search([]byte("cat")) println("cat:", v, ok) v, ok = t.Search([]byte("car")) println("car:", v, ok) + } diff --git a/docs/weekly/angelo/week_4.md b/docs/weekly/angelo/week_4.md new file mode 100644 index 0000000..db281f3 --- /dev/null +++ b/docs/weekly/angelo/week_4.md @@ -0,0 +1,8 @@ +## Angelo's Progress +* implemented the insert function +* implemented update function in addchild + + +## What's next? +* implement grow to transition to larger nodes +* fix the bugs in insert diff --git a/internal/art/insert.go b/internal/art/insert.go index b606a1a..0fcd0ad 100644 --- a/internal/art/insert.go +++ b/internal/art/insert.go @@ -9,41 +9,90 @@ func insert(n *Node, value string, key []byte, depth int) *Node { new_node := newNode4() oldkey := n.leaf.key i := depth + for i < len(oldkey) && i < len(key) && oldkey[i] == key[i] { - new_node.innerNode.meta.prefix[i-depth] = key[i] + prefix_index := i - depth + if prefix_index < maxprefixlen { + new_node.innerNode.meta.prefix[prefix_index] = key[i] // stores only the till max prefix + + } + i++ } - new_node.innerNode.meta.prefixlen = i - depth + new_node.innerNode.meta.prefixlen = i - depth // stores full prefix len even after maxprefixlen depth = i + if depth == len(key) { + new_node.innerNode.leaf = newleaf(value, key) + + } else { + new_node = addchild(new_node, key[depth], newleaf(value, key)) + + } + if depth == len(oldkey) { + new_node.innerNode.leaf = n + + } else { + new_node = addchild(new_node, oldkey[depth], n) + + } - new_node = addchild(new_node, keycheck(key, depth), newleaf(value, key)) - new_node = addchild(new_node, keycheck(oldkey, depth), n) return new_node } p := checkprefix(n, key, depth) + if p != n.innerNode.meta.prefixlen { + new_node := newNode4() - new_node = addchild(new_node, keycheck(key, depth+p), newleaf(value, key)) - new_node = addchild(new_node, n.innerNode.meta.prefix[p], n) + if p+depth == len(key) { + new_node.innerNode.leaf = newleaf(value, key) + + } else { + new_node = addchild(new_node, key[depth+p], newleaf(value, key)) + + } + leaf := fetchleaf(n) // either its an actual leaf or innernode leaf + oldkey := leaf.leaf.key + + var oldkeybyte byte + if p < maxprefixlen { + oldkeybyte = n.innerNode.meta.prefix[p] + } else { + oldkeybyte = oldkey[depth+p] + } + + new_node = addchild(new_node, oldkeybyte, n) + new_node.innerNode.meta.prefixlen = p - copy(new_node.innerNode.meta.prefix, n.innerNode.meta.prefix[:p]) + if p < maxprefixlen { + copy(new_node.innerNode.meta.prefix, n.innerNode.meta.prefix[:p]) + + } else { + copy(new_node.innerNode.meta.prefix, n.innerNode.meta.prefix[:maxprefixlen]) + } oldprefixlen := n.innerNode.meta.prefixlen - n.innerNode.meta.prefixlen = n.innerNode.meta.prefixlen - (p + 1) - copy(n.innerNode.meta.prefix, n.innerNode.meta.prefix[p+1:oldprefixlen]) + n.innerNode.meta.prefixlen = oldprefixlen - (p + 1) + if len(n.innerNode.meta.prefix[p+1:oldprefixlen]) < maxprefixlen { + copy(n.innerNode.meta.prefix, n.innerNode.meta.prefix[p+1:oldprefixlen]) + + } else { + leaf := fetchleaf(n) + copy(n.innerNode.meta.prefix, leaf.leaf.key[depth+p+1:depth+p+1+maxprefixlen]) + } + return new_node } depth += n.innerNode.meta.prefixlen - next, pos := findchild(keycheck(key, depth), n) + next, pos := findchild(key[depth], n) if next != nil { n.innerNode.children[pos] = insert(next, value, key, depth+1) return n } else { - n = addchild(n, keycheck(key, depth), newleaf(value, key)) + n = addchild(n, key[depth], newleaf(value, key)) return n } diff --git a/internal/art/node.go b/internal/art/node.go index e41ecd5..b381512 100644 --- a/internal/art/node.go +++ b/internal/art/node.go @@ -25,11 +25,14 @@ type Node struct { } type innerNode struct { - nodeType NodeType - keys []byte - children []*Node + nodeType NodeType + keys []byte + children []*Node + leaf *Node + num_children int - meta meta + + meta meta } type meta struct { diff --git a/internal/art/print_tree.go b/internal/art/print_tree.go index 758150b..5a332b6 100644 --- a/internal/art/print_tree.go +++ b/internal/art/print_tree.go @@ -2,7 +2,7 @@ package art import "fmt" -func PrintTree(n *Node, level int) { +func PrintTree(n *Node, level int, depth int) { if n == nil { return } @@ -20,20 +20,28 @@ func PrintTree(n *Node, level int) { in := n.innerNode prefixlen := in.meta.prefixlen - if prefixlen < 0 || prefixlen > len(in.meta.prefix) { - prefixlen = 0 - } + prefix := "" + if prefixlen <= maxprefixlen { + prefix = string(in.meta.prefix[:prefixlen]) - prefix := string(in.meta.prefix[:prefixlen]) + } else { + leaf := fetchleaf(n) + prefix = string(leaf.leaf.key[depth : depth+prefixlen]) + } fmt.Println(indent+"Node(prefix=\""+prefix+"\", prefixLen=", prefixlen, ")") + if in.leaf != nil { + fmt.Printf("%s [Internal Leaf]: %s\n", indent, string(in.leaf.leaf.key)) + } // Print children + + newDepth := depth + prefixlen for i := 0; i < len(in.keys); i++ { if in.children[i] != nil { fmt.Printf("%s Edge('%c' | %d):\t", indent, in.keys[i], in.keys[i]) - PrintTree(in.children[i], level+1) + PrintTree(in.children[i], level+1, newDepth+1) } } } diff --git a/internal/art/search.go b/internal/art/search.go index 34472f3..453a3c2 100644 --- a/internal/art/search.go +++ b/internal/art/search.go @@ -1,14 +1,10 @@ package art func search(n *Node, key []byte, depth int) *Node { - // Base case: nil node means we've reached a dead end. - // Key does not exist in this path of the tree. if n == nil { return nil } - // Reached a leaf node, do a full key comparison. - // Necessary because path compression may have skipped bytes. if isleaf(n) { if string(n.leaf.key) == string(key) { return n @@ -16,22 +12,25 @@ func search(n *Node, key []byte, depth int) *Node { return nil } - // Check if the compressed prefix at this node matches the search key. - // If any byte mismatches, the entire subtree is irrelevant. if n.innerNode.meta.prefixlen > 0 { p := checkprefix(n, key, depth) + if p != n.innerNode.meta.prefixlen { return nil } depth += n.innerNode.meta.prefixlen } - // Get the next byte to branch on at current depth. - // Returns 0 (terminator) if key is exhausted. - k := keycheck(key, depth) + // KEY EXHAUSTION CHECK (The Fix) + // If we've consumed the entire key, the value must be in this inner node's leaf + if depth == len(key) { + if n.innerNode.leaf != nil { + return n.innerNode.leaf + } + return nil + } - // Find the child corresponding to byte k and recurse deeper. - // Return nil if no child exists for this byte. + k := key[depth] next, _ := findchild(k, n) if next != nil { return search(next, key, depth+1) diff --git a/internal/art/util.go b/internal/art/util.go index 5f6a3cb..8d8c463 100644 --- a/internal/art/util.go +++ b/internal/art/util.go @@ -31,10 +31,29 @@ func addchild(n *Node, k byte, child *Node) *Node { func checkprefix(n *Node, key []byte, depth int) int { in := n.innerNode var i int - for i = 0; i < in.meta.prefixlen && in.meta.prefix[i] == keycheck(key, depth+i); i++ { //checks prefix until mismatch + maxcmp := min(maxprefixlen, in.meta.prefixlen) + + for i = 0; i < maxcmp && depth+i < len(key); i++ { //checks prefix until mismatch + if in.meta.prefix[i] != key[depth+i] { + return i // case when you find mismatch and the mismatch is less than maxprefixlen + + } + + } + if in.meta.prefixlen > maxprefixlen { + leaf := fetchleaf(n) + leafkey := leaf.leaf.key + for ; i < in.meta.prefixlen && depth+i < len(leafkey) && depth+i < len(key); i++ { + if key[depth+i] != leaf.leaf.key[depth+i] { + return i // case when you find mismatch and the mismatch is more than maxprefixlen + + } + + } } - return i + + return i // case when you find mismatch and the mismatch is equal maxprefixlen } @@ -42,7 +61,7 @@ func findchild(k byte, n *Node) (*Node, int) { in := n.innerNode switch in.nodeType { case Node4, Node16: - for i := 0; i < len(in.keys); i++ { + for i := 0; i < in.num_children; i++ { if in.keys[i] == k { return in.children[i], i //finds the node and the position } @@ -64,14 +83,6 @@ func findchild(k byte, n *Node) (*Node, int) { return nil, -1 } -func keycheck(key []byte, depth int) byte { - if depth >= len(key) { - return 0 - - } else { - return key[depth] - } -} // removechild removes the child with key k from node n by shifting // all subsequent keys and children left to fill the gap. @@ -100,10 +111,17 @@ func grow(n *Node) *Node { case Node4: n16 := newNode16() copymeta(n, n16) + index := 0 for i := 0; i < 4; i++ { - n16.innerNode.keys[i] = n.innerNode.keys[i] - n16.innerNode.children[i] = n.innerNode.children[i] + if n.innerNode.children[i] != nil { + n16.innerNode.keys[index] = n.innerNode.keys[i] + n16.innerNode.children[index] = n.innerNode.children[i] + index++ + + } + } + n16.innerNode.num_children = index return n16 case Node16: n48 := newNode48() @@ -122,6 +140,7 @@ func grow(n *Node) *Node { } } + n48.innerNode.num_children = index return n48 case Node48: @@ -136,6 +155,7 @@ func grow(n *Node) *Node { } } + return n256 } @@ -147,3 +167,22 @@ func copymeta(n *Node, new_node *Node) { new_node.innerNode.meta.prefixlen = n.innerNode.meta.prefixlen } + +func fetchleaf(n *Node) *Node { + if isleaf(n) { + return n + } + if n.innerNode.leaf != nil { + return n.innerNode.leaf + } + + for i := 0; i < len(n.innerNode.keys); i++ { + if n.innerNode.children[i] != nil { + return fetchleaf(n.innerNode.children[i]) + + } + + } + return nil + +} From f12534e977d6c3f22f977ed5d2b6f3d405ebc1e3 Mon Sep 17 00:00:00 2001 From: BhuvignaReddy A T Date: Mon, 30 Mar 2026 23:20:57 +0530 Subject: [PATCH 17/26] fix: correct key exhaustion bug in deletekey and remove keycheck func call --- internal/art/art.go | 10 ++++++++-- internal/art/delete.go | 14 +++++++++++--- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/internal/art/art.go b/internal/art/art.go index ff90989..7c15ea3 100644 --- a/internal/art/art.go +++ b/internal/art/art.go @@ -21,11 +21,17 @@ func (t *Tree) Search(key []byte) (string, bool) { } return "", false } - func (t *Tree) Delete(key []byte) bool { + if t.root == nil { + return false + } + newRoot, deleted := deletekey(t.root, key, 0) + if deleted { t.root = newRoot + return true } - return deleted + + return false } diff --git a/internal/art/delete.go b/internal/art/delete.go index 026ead8..4cd41ea 100644 --- a/internal/art/delete.go +++ b/internal/art/delete.go @@ -1,7 +1,5 @@ package art -// deletekey recursively walks the tree to find and remove the given key. -// Returns the (possibly modified) node and whether the key was deleted. func deletekey(n *Node, key []byte, depth int) (*Node, bool) { if n == nil { return nil, false @@ -20,7 +18,17 @@ func deletekey(n *Node, key []byte, depth int) (*Node, bool) { } depth += n.innerNode.meta.prefixlen - k := keycheck(key, depth) + // 3. KEY EXHAUSTION (The Fix) + // If the key ends here, the value is in the inner node's leaf field + if depth == len(key) { + if n.innerNode.leaf != nil { + n.innerNode.leaf = nil // Remove the value + return n, true + } + return n, false + } + + k := key[depth] child, pos := findchild(k, n) if child == nil { return n, false From ab066c609646f825ce28aac68a23bfd93bbe741a Mon Sep 17 00:00:00 2001 From: angelo Date: Tue, 31 Mar 2026 23:59:41 +0530 Subject: [PATCH 18/26] chore: added deep copy to prevent to make new_node and current node independent and other small changes --- cmd/radFS/arttest/main.go | 12 ++++++------ internal/art/insert.go | 14 +++++++++----- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/cmd/radFS/arttest/main.go b/cmd/radFS/arttest/main.go index be2817b..1d801ac 100644 --- a/cmd/radFS/arttest/main.go +++ b/cmd/radFS/arttest/main.go @@ -1,17 +1,17 @@ package main import ( + "fmt" + "github.com/acmpesuecc/radFS/internal/art" ) func main() { tree := &art.Tree{} - tree.Insert([]byte("int"), "first") - - tree.Insert([]byte("intern"), "second") - tree.Insert([]byte("internet"), "first") - tree.Insert([]byte("i"), "first") - + for i := 0; i < 50; i++ { + key := []byte("ca" + string(byte(i+97))) + tree.Insert(key, fmt.Sprintf("val%d", i)) + } art.PrintTree(tree.Root(), 0, 0) } diff --git a/internal/art/insert.go b/internal/art/insert.go index 0fcd0ad..0694a0f 100644 --- a/internal/art/insert.go +++ b/internal/art/insert.go @@ -66,26 +66,30 @@ func insert(n *Node, value string, key []byte, depth int) *Node { new_node.innerNode.meta.prefixlen = p if p < maxprefixlen { - copy(new_node.innerNode.meta.prefix, n.innerNode.meta.prefix[:p]) + new_node.innerNode.meta.prefix = deepcopy(n.innerNode.meta.prefix[:p]) } else { - copy(new_node.innerNode.meta.prefix, n.innerNode.meta.prefix[:maxprefixlen]) + new_node.innerNode.meta.prefix = deepcopy(n.innerNode.meta.prefix[:maxprefixlen]) } oldprefixlen := n.innerNode.meta.prefixlen n.innerNode.meta.prefixlen = oldprefixlen - (p + 1) - if len(n.innerNode.meta.prefix[p+1:oldprefixlen]) < maxprefixlen { - copy(n.innerNode.meta.prefix, n.innerNode.meta.prefix[p+1:oldprefixlen]) + if oldprefixlen < maxprefixlen { + n.innerNode.meta.prefix = deepcopy(n.innerNode.meta.prefix[p+1 : oldprefixlen]) } else { leaf := fetchleaf(n) - copy(n.innerNode.meta.prefix, leaf.leaf.key[depth+p+1:depth+p+1+maxprefixlen]) + n.innerNode.meta.prefix = deepcopy(leaf.leaf.key[depth+p+1 : depth+p+1+maxprefixlen]) } return new_node } depth += n.innerNode.meta.prefixlen + if depth == len(key) { + n.innerNode.leaf = newleaf(value, key) + return n + } next, pos := findchild(key[depth], n) if next != nil { n.innerNode.children[pos] = insert(next, value, key, depth+1) From 277c89c5909602ed868746165cca9a3275f3983b Mon Sep 17 00:00:00 2001 From: angelo Date: Wed, 1 Apr 2026 00:33:50 +0530 Subject: [PATCH 19/26] chore:forgot to add deepcopy func from utilty --- internal/art/print_tree.go | 34 ++++++++++++++++++++++++++++++---- internal/art/util.go | 12 +++++++++++- 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/internal/art/print_tree.go b/internal/art/print_tree.go index 5a332b6..6f78ff3 100644 --- a/internal/art/print_tree.go +++ b/internal/art/print_tree.go @@ -37,11 +37,37 @@ func PrintTree(n *Node, level int, depth int) { // Print children newDepth := depth + prefixlen - for i := 0; i < len(in.keys); i++ { - if in.children[i] != nil { - fmt.Printf("%s Edge('%c' | %d):\t", indent, in.keys[i], in.keys[i]) - PrintTree(in.children[i], level+1, newDepth+1) + switch in.nodeType { + case Node4, Node16: + for i := 0; i < in.num_children; i++ { + key := in.keys[i] + child := in.children[i] + + fmt.Printf("%s Edge('%c' | %d):\t", indent, key, key) + PrintTree(child, level+1, newDepth+1) + } + + case Node48: + for b := 0; b < 256; b++ { + idx := in.keys[b] + + if idx != 0 { + + child := in.children[idx-1] + + fmt.Printf("%s Edge('%c' | %d):\t", indent, byte(b), b) + PrintTree(child, level+1, newDepth+1) + } + } + + case Node256: + for b := 0; b < 256; b++ { + child := in.children[b] + if child != nil { + fmt.Printf("%s Edge('%c' | %d):\t", indent, byte(b), b) + PrintTree(child, level+1, newDepth+1) + } } } } diff --git a/internal/art/util.go b/internal/art/util.go index dac41d0..b0d67ff 100644 --- a/internal/art/util.go +++ b/internal/art/util.go @@ -142,8 +142,10 @@ func grow(n *Node) *Node { } func copymeta(n *Node, new_node *Node) { - new_node.innerNode.meta.prefix = n.innerNode.meta.prefix + new_node.innerNode.meta.prefixlen = n.innerNode.meta.prefixlen + new_node.innerNode.meta.prefix = deepcopy(n.innerNode.meta.prefix[:min(n.innerNode.meta.prefixlen, maxprefixlen)]) + new_node.innerNode.leaf = n.innerNode.leaf } @@ -165,3 +167,11 @@ func fetchleaf(n *Node) *Node { return nil } + +func deepcopy(source []byte) []byte { + + desarr := make([]byte, maxprefixlen) + copy(desarr, source) + return desarr + +} From b423a0c92d3c1f48ca724739749dec7551092896 Mon Sep 17 00:00:00 2001 From: angelo Date: Wed, 1 Apr 2026 00:52:02 +0530 Subject: [PATCH 20/26] chore : made addchild and fetchleaf compatible with multiple node types --- internal/art/util.go | 94 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 74 insertions(+), 20 deletions(-) diff --git a/internal/art/util.go b/internal/art/util.go index b0d67ff..c0849b0 100644 --- a/internal/art/util.go +++ b/internal/art/util.go @@ -4,29 +4,66 @@ package art func addchild(n *Node, k byte, child *Node) *Node { in := n.innerNode - child1, pos1 := findchild(k, n) + child1, pos1 := findchild(k, n) // to prevent duplicate insertions if child1 != nil { in.children[pos1] = child return n } + switch n.innerNode.nodeType { + case Node16, Node4: + if n.innerNode.num_children == len(in.keys) { + n = grow(n) - if n.innerNode.num_children == len(in.keys) { - n = grow(n) - in = n.innerNode + return addchild(n, k, child) - } + } + var i int + for i = in.num_children - 1; i >= 0 && k < in.keys[i]; i-- { //shifts until keybyte place is found + in.keys[i+1] = in.keys[i] + in.children[i+1] = in.children[i] - var i int - for i = in.num_children - 1; i >= 0 && in.keys[i] > k; i-- { - in.keys[i+1] = in.keys[i] - in.children[i+1] = in.children[i] + } - } + in.keys[i+1] = k + in.children[i+1] = child + in.num_children++ - in.keys[i+1] = k - in.children[i+1] = child - in.num_children += 1 + return n + case Node48: + if n.innerNode.num_children == len(in.children) { + n = grow(n) + return addchild(n, k, child) + + } + if in.keys[k] != 0 { // if key exist then update + key := int(n.innerNode.keys[k]) - 1 // the zero slot is used to check if its an empty key so we start filling the index values in key from 1 + n.innerNode.children[key] = child + return n + + } + + for i := 0; i < len(in.children); i++ { // find the free child + if in.children[i] == nil { + in.children[i] = child + in.keys[k] = byte(i + 1) + in.num_children++ + + break + + } + + } + case Node256: + if in.children[k] != nil { //update key + in.children[k] = child + return n + + } + in.children[k] = child //inserting new key + in.num_children++ + + } return n } @@ -106,12 +143,12 @@ func grow(n *Node) *Node { n48 := newNode48() copymeta(n, n48) index := 0 - for i := 0; i < 16; i++ { + for i := 0; i < n.innerNode.num_children; i++ { idx := n.innerNode.keys[i] child := n.innerNode.children[i] if child != nil { - n48.innerNode.keys[idx] = byte(index + 1) // the reason its index+1 is because we are making 0 a kind of "no children" case + n48.innerNode.keys[idx] = byte(index + 1) // the reason its index+1 is because we are making 0 a kind of "no children" case since arrays are automatically init to zero n48.innerNode.children[index] = child index++ @@ -125,15 +162,18 @@ func grow(n *Node) *Node { case Node48: n256 := newNode256() copymeta(n, n256) + count := 0 for i := 0; i < 256; i++ { idx := n.innerNode.keys[i] if n.innerNode.keys[i] != 0 { child := n.innerNode.children[int(idx-1)] n256.innerNode.children[i] = child + count++ } } + n256.innerNode.num_children = count return n256 @@ -157,12 +197,26 @@ func fetchleaf(n *Node) *Node { return n.innerNode.leaf } - for i := 0; i < len(n.innerNode.keys); i++ { - if n.innerNode.children[i] != nil { - return fetchleaf(n.innerNode.children[i]) - + in := n.innerNode + switch in.nodeType { + case Node4, Node16: + for i := 0; i < in.num_children; i++ { + if in.children[i] != nil { + return fetchleaf(in.children[i]) + } + } + case Node48: + for _, child := range in.children { + if child != nil { + return fetchleaf(child) + } + } + case Node256: + for i := 0; i < len(in.children); i++ { + if in.children[i] != nil { + return fetchleaf(in.children[i]) + } } - } return nil From ffc8e5cd38b2330448f25c992ff8ea98c1c1c6f5 Mon Sep 17 00:00:00 2001 From: BhuvignaReddy A T Date: Wed, 1 Apr 2026 22:47:37 +0530 Subject: [PATCH 21/26] fix: Perform deepcopy of metadata -update copymeta func --- internal/art/util.go | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/internal/art/util.go b/internal/art/util.go index 8d8c463..56c2a42 100644 --- a/internal/art/util.go +++ b/internal/art/util.go @@ -132,8 +132,7 @@ func grow(n *Node) *Node { child := n.innerNode.children[i] if child != nil { - n48.innerNode.keys[idx] = byte(index + 1) // the reason its index+1 is because we are making 0 a kind of "no children" case - + n48.innerNode.keys[idx] = byte(index + 1) n48.innerNode.children[index] = child index++ @@ -163,9 +162,15 @@ func grow(n *Node) *Node { } func copymeta(n *Node, new_node *Node) { - new_node.innerNode.meta.prefix = n.innerNode.meta.prefix + new_node.innerNode.meta.prefixlen = n.innerNode.meta.prefixlen + limit := n.innerNode.meta.prefixlen + if limit > maxprefixlen { + limit = maxprefixlen + } + + copy(new_node.innerNode.meta.prefix, n.innerNode.meta.prefix[:limit]) } func fetchleaf(n *Node) *Node { From e256dfd148e2ea16cbd63d661cee7790bcd5e5ff Mon Sep 17 00:00:00 2001 From: BhuvignaReddy A T Date: Thu, 2 Apr 2026 21:50:21 +0530 Subject: [PATCH 22/26] fix: upgrade removechild func to handle different nodeTypes while shrinking --- internal/art/util.go | 90 ++++++++++++++++++++++++-------------------- 1 file changed, 49 insertions(+), 41 deletions(-) diff --git a/internal/art/util.go b/internal/art/util.go index 922b065..7f75a54 100644 --- a/internal/art/util.go +++ b/internal/art/util.go @@ -3,12 +3,9 @@ package art // TODO: Helper functions (e.g., prefix matching) func addchild(n *Node, k byte, child *Node) *Node { in := n.innerNode -<<<<<<< HEAD child1, pos1 := findchild(k, n) // to prevent duplicate insertions -======= - child1, pos1 := findchild(k, n) ->>>>>>> ffc8e5cd38b2330448f25c992ff8ea98c1c1c6f5 + if child1 != nil { in.children[pos1] = child return n @@ -68,20 +65,7 @@ func addchild(n *Node, k byte, child *Node) *Node { in.num_children++ } -<<<<<<< HEAD -======= - var i int - for i = in.num_children - 1; i >= 0 && in.keys[i] > k; i-- { - in.keys[i+1] = in.keys[i] - in.children[i+1] = in.children[i] - - } - - in.keys[i+1] = k - in.children[i+1] = child - in.num_children += 1 ->>>>>>> ffc8e5cd38b2330448f25c992ff8ea98c1c1c6f5 return n } @@ -141,26 +125,60 @@ func findchild(k byte, n *Node) (*Node, int) { } -// removechild removes the child with key k from node n by shifting -// all subsequent keys and children left to fill the gap. -func removechild(n *Node, k byte) { +func removechild(n *Node, k byte) *Node { + in := n.innerNode _, pos := findchild(k, n) - if pos == -1 { - return // key not found, nothing to remove + + // If child doesn't exist, return original node (search loop only for node 4 and 16) + if pos == -1 && in.nodeType <= Node16 { + return n } - in := n.innerNode - last := len(in.keys) - 1 + switch in.nodeType { + case Node4, Node16: + + for i := pos; i < in.num_children-1; i++ { + in.keys[i] = in.keys[i+1] + in.children[i] = in.children[i+1] + } + in.keys[in.num_children-1] = 0 + in.children[in.num_children-1] = nil + + case Node48: + + idx := in.keys[k] + if idx > 0 { + in.keys[k] = 0 + in.children[idx-1] = nil + } + + case Node256: - // shift everything after pos one step to the left - for i := pos; i < last; i++ { - in.keys[i] = in.keys[i+1] - in.children[i] = in.children[i+1] + in.children[k] = nil } - // clear the now-duplicate last slot to avoid stale pointers - in.keys[last] = 0 - in.children[last] = nil + in.num_children-- + + if shouldShrink(n) { + return shrink(n) + } + + return n +} + +func shouldShrink(n *Node) bool { + in := n.innerNode + switch in.nodeType { + case Node256: + return in.num_children <= 48 + case Node48: + return in.num_children <= 16 + case Node16: + return in.num_children <= 4 + case Node4: + return in.num_children <= 1 + } + return false } func grow(n *Node) *Node { @@ -189,12 +207,8 @@ func grow(n *Node) *Node { child := n.innerNode.children[i] if child != nil { -<<<<<<< HEAD - n48.innerNode.keys[idx] = byte(index + 1) // the reason its index+1 is because we are making 0 a kind of "no children" case since arrays are automatically init to zero -======= n48.innerNode.keys[idx] = byte(index + 1) ->>>>>>> ffc8e5cd38b2330448f25c992ff8ea98c1c1c6f5 n48.innerNode.children[index] = child index++ @@ -232,12 +246,6 @@ func copymeta(n *Node, new_node *Node) { new_node.innerNode.meta.prefix = deepcopy(n.innerNode.meta.prefix[:min(n.innerNode.meta.prefixlen, maxprefixlen)]) new_node.innerNode.leaf = n.innerNode.leaf - limit := n.innerNode.meta.prefixlen - if limit > maxprefixlen { - limit = maxprefixlen - } - - copy(new_node.innerNode.meta.prefix, n.innerNode.meta.prefix[:limit]) } func fetchleaf(n *Node) *Node { From 6309e6cbd99fbc9a1ce419fa7d8a4c1df12075e5 Mon Sep 17 00:00:00 2001 From: BhuvignaReddy A T Date: Thu, 2 Apr 2026 22:36:17 +0530 Subject: [PATCH 23/26] feat: Defined shrink func , need to fix node4-> leaf collapse logic --- internal/art/util.go | 61 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/internal/art/util.go b/internal/art/util.go index 7f75a54..1e1084e 100644 --- a/internal/art/util.go +++ b/internal/art/util.go @@ -240,6 +240,67 @@ func grow(n *Node) *Node { return nil } + +func shrink(n *Node) *Node { + in := n.innerNode + switch in.nodeType { + case Node4: + if in.num_children == 0 { + if in.leaf != nil { + return in.leaf + } + + return nil + } + if in.num_children == 1 && in.leaf == nil { + return in.children[0] + } + + return n + + case Node16: + n4 := newNode4() + copymeta(n, n4) + for i := 0; i < in.num_children; i++ { + n4.innerNode.keys[i] = in.keys[i] + n4.innerNode.children[i] = in.children[i] + } + n4.innerNode.num_children = in.num_children + return n4 + + case Node48: + n16 := newNode16() + copymeta(n, n16) + count := 0 + for i := 0; i < 256; i++ { + idx := in.keys[i] + if idx > 0 { + n16.innerNode.keys[count] = byte(i) + n16.innerNode.children[count] = in.children[idx-1] + count++ + } + } + n16.innerNode.num_children = count + return n16 + + case Node256: + n48 := newNode48() + copymeta(n, n48) + count := 0 + for i := 0; i < 256; i++ { + child := in.children[i] + if child != nil { + n48.innerNode.children[count] = child + n48.innerNode.keys[byte(i)] = byte(count + 1) + count++ + } + } + n48.innerNode.num_children = count + return n48 + } + return n +} + func copymeta(n *Node, new_node *Node) { new_node.innerNode.meta.prefixlen = n.innerNode.meta.prefixlen From a24f54e869ffe2c9c6aaa5a2f3b702786d0fe045 Mon Sep 17 00:00:00 2001 From: BhuvignaReddy A T Date: Fri, 3 Apr 2026 10:49:38 +0530 Subject: [PATCH 24/26] fix: Capturing the shrunk node (n=removechild(n,k) and returning n --- internal/art/delete.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/art/delete.go b/internal/art/delete.go index 4cd41ea..dbd24d2 100644 --- a/internal/art/delete.go +++ b/internal/art/delete.go @@ -40,7 +40,7 @@ func deletekey(n *Node, key []byte, depth int) (*Node, bool) { } if newChild == nil { - removechild(n, k) + n = removechild(n, k) } else { n.innerNode.children[pos] = newChild } From a8aa252c62d4acd1518291396d512d43e2bd5dcd Mon Sep 17 00:00:00 2001 From: BhuvignaReddy A T Date: Fri, 3 Apr 2026 11:26:06 +0530 Subject: [PATCH 25/26] chore: Update main.go to test Growth and shrink functioning of diff nodeTypes --- cmd/radFS/arttest/main.go | 88 ++++++++++++++++++++++++++++++--------- 1 file changed, 69 insertions(+), 19 deletions(-) diff --git a/cmd/radFS/arttest/main.go b/cmd/radFS/arttest/main.go index 5d7d648..2e76693 100644 --- a/cmd/radFS/arttest/main.go +++ b/cmd/radFS/arttest/main.go @@ -9,28 +9,78 @@ import ( func main() { t := &art.Tree{} -<<<<<<< HEAD - for i := 0; i < 50; i++ { - key := []byte("ca" + string(byte(i+97))) - tree.Insert(key, fmt.Sprintf("val%d", i)) + fmt.Println("====== PHASE 1: STRESS TESTING GROWTH (4 -> 16 -> 48 -> 256) ======") + + // We use keys with a single byte difference to ensure they all go into the SAME internal node + for i := 0; i < 256; i++ { + key := []byte{byte(i)} + t.Insert(key, fmt.Sprintf("%d", i)) + + switch i + 1 { + + case 5: + fmt.Printf("[Check] Inserted 5 keys. (Current: %d children)\n", i+1) + fmt.Printf("NodeType: %s\n", art.GetNodeTypeName(t.Root())) + case 17: + fmt.Printf("[Check] Inserted 17 keys. (Current: %d children)\n", i+1) + fmt.Printf("NodeType: %s\n", art.GetNodeTypeName(t.Root())) + case 49: + fmt.Printf("[Check] Inserted 49 keys. (Current: %d children)\n", i+1) + fmt.Printf("NodeType: %s\n", art.GetNodeTypeName(t.Root())) + } + } + + fmt.Printf("Final Growth State: %d children in root.\n", 256) + + fmt.Println("\n===== PHASE 2: SEARCH VERIFICATION =====") + // Verify we didn't lose data during the messy pointer copying in grow/shrink + testKeys := []int{0, 15, 47, 100, 255} + for _, tk := range testKeys { + val, ok := t.Search([]byte{byte(tk)}) + fmt.Printf("Searching for key %d: Found=%v, Value=%v\n", tk, ok, val) + } + + fmt.Println("\n===== PHASE 3: STRESS TESTING SHRINK (256 -> 48 -> 16 -> 4) =====") + + for i := 255; i >= 37; i-- { + t.Delete([]byte{byte(i)}) } - art.PrintTree(tree.Root(), 0, 0) -======= - - t.Insert([]byte("cart"), "v1") - t.Insert([]byte("car"), "v2") - t.Insert([]byte("cab"), "v3") - println("===Tree before deletion===") - art.PrintTree(t.Root(),0 ,0) - t.Delete([]byte("car")) - println("===Tree after deletion of 'car'===") + fmt.Println("[Check] Deleted down to 37 keys") + fmt.Printf("NodeType: %s\n", art.GetNodeTypeName(t.Root())) + + for i := 36; i >= 12; i-- { + t.Delete([]byte{byte(i)}) + } + fmt.Println("[Check] Deleted down to 12 keys. ") + fmt.Printf("NodeType: %s\n", art.GetNodeTypeName(t.Root())) + + for i := 11; i >= 3; i-- { + t.Delete([]byte{byte(i)}) + } + fmt.Println("[Check] Deleted down to 3 keys. ") + fmt.Printf("NodeType: %s\n", art.GetNodeTypeName(t.Root())) + + fmt.Println("\n===== PHASE 4: FULL COLLAPSE (Node4 -> Leaf -> Nil) =====") + + // Delete until only 1 key remains (should collapse Node4 into a Leaf) + for i := 2; i >= 1; i-- { + t.Delete([]byte{byte(i)}) + } + fmt.Println("[Check] Deleted down to 1 key. Tree should be a single Leaf (no Internal Node).") art.PrintTree(t.Root(), 0, 0) - v, ok := t.Search([]byte("cat")) - println("cat:", v, ok) - v, ok = t.Search([]byte("car")) - println("car:", v, ok) + // Delete the very last key + t.Delete([]byte{byte(0)}) + fmt.Println("[Check] Deleted last key. Root should be nil.") + + if t.Root() == nil { + fmt.Println("SUCCESS: Tree is fully empty (Root is nil).") + } else { + fmt.Println("WARNING: Root is not nil. Check your Node4 -> Leaf collapse logic.") + art.PrintTree(t.Root(), 0, 0) + } + fmt.Println("\n===== FINAL TREE STRUCTURE =====") + fmt.Printf("NodeType: %s\n", art.GetNodeTypeName(t.Root())) ->>>>>>> ffc8e5cd38b2330448f25c992ff8ea98c1c1c6f5 } From 35d2dca310429b4fa01265f00bcc217607bf3a71 Mon Sep 17 00:00:00 2001 From: BhuvignaReddy A T Date: Fri, 3 Apr 2026 11:27:26 +0530 Subject: [PATCH 26/26] feat: Define GetNodeTypeName func to check the current nodetype in main.go --- internal/art/art.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/internal/art/art.go b/internal/art/art.go index 7c15ea3..56cea73 100644 --- a/internal/art/art.go +++ b/internal/art/art.go @@ -35,3 +35,15 @@ func (t *Tree) Delete(key []byte) bool { return false } + +func GetNodeTypeName(n *Node) string { + if n == nil { + return "Nil" + } + if isleaf(n) { + return "Leaf" + } + + types := []string{"Node4", "Node16", "Node48", "Node256"} + return types[n.innerNode.nodeType] +}