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/10] 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/10] 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/10] 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/10] 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/10] 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/10] 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/10] 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/10] 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/10] 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 0352d08db8092b4443201b47b753953b5190bb5b Mon Sep 17 00:00:00 2001 From: angelo Date: Fri, 27 Mar 2026 20:14:30 +0530 Subject: [PATCH 10/10] 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 + +}