diff --git a/examples/gno.land/p/demo/avl/node.gno b/examples/gno.land/p/demo/avl/node.gno index a49dba00ae1..7308e163768 100644 --- a/examples/gno.land/p/demo/avl/node.gno +++ b/examples/gno.land/p/demo/avl/node.gno @@ -3,15 +3,17 @@ package avl //---------------------------------------- // Node +// Node represents a node in an AVL tree. type Node struct { - key string - value interface{} - height int8 - size int - leftNode *Node - rightNode *Node + key string // key is the unique identifier for the node. + value interface{} // value is the data stored in the node. + height int8 // height is the height of the node in the tree. + size int // size is the number of nodes in the subtree rooted at this node. + leftNode *Node // leftNode is the left child of the node. + rightNode *Node // rightNode is the right child of the node. } +// NewNode creates a new node with the given key and value. func NewNode(key string, value interface{}) *Node { return &Node{ key: key, @@ -21,6 +23,7 @@ func NewNode(key string, value interface{}) *Node { } } +// Size returns the size of the subtree rooted at the node. func (node *Node) Size() int { if node == nil { return 0 @@ -28,18 +31,22 @@ func (node *Node) Size() int { return node.size } +// IsLeaf checks if the node is a leaf node (has no children). func (node *Node) IsLeaf() bool { return node.height == 0 } +// Key returns the key of the node. func (node *Node) Key() string { return node.key } +// Value returns the value of the node. func (node *Node) Value() interface{} { return node.value } +// _copy creates a copy of the node (excluding value). func (node *Node) _copy() *Node { if node.height == 0 { panic("Why are you copying a value node?") @@ -53,6 +60,7 @@ func (node *Node) _copy() *Node { } } +// Has checks if a node with the given key exists in the subtree rooted at the node. func (node *Node) Has(key string) (has bool) { if node == nil { return false @@ -62,102 +70,114 @@ func (node *Node) Has(key string) (has bool) { } if node.height == 0 { return false - } else { - if key < node.key { - return node.getLeftNode().Has(key) - } else { - return node.getRightNode().Has(key) - } } + if key < node.key { + return node.getLeftNode().Has(key) + } + return node.getRightNode().Has(key) } +// Get searches for a node with the given key in the subtree rooted at the node +// and returns its index, value, and whether it exists. func (node *Node) Get(key string) (index int, value interface{}, exists bool) { if node == nil { return 0, nil, false } + if node.height == 0 { if node.key == key { return 0, node.value, true - } else if node.key < key { - return 1, nil, false - } else { - return 0, nil, false } - } else { - if key < node.key { - return node.getLeftNode().Get(key) - } else { - rightNode := node.getRightNode() - index, value, exists = rightNode.Get(key) - index += node.size - rightNode.size - return index, value, exists + if node.key < key { + return 1, nil, false } + return 0, nil, false + } + + if key < node.key { + return node.getLeftNode().Get(key) } + + rightNode := node.getRightNode() + index, value, exists = rightNode.Get(key) + index += node.size - rightNode.size + return index, value, exists } +// GetByIndex retrieves the key-value pair of the node at the given index +// in the subtree rooted at the node. func (node *Node) GetByIndex(index int) (key string, value interface{}) { if node.height == 0 { if index == 0 { return node.key, node.value - } else { - panic("GetByIndex asked for invalid index") - return "", nil - } - } else { - // TODO: could improve this by storing the sizes - leftNode := node.getLeftNode() - if index < leftNode.size { - return leftNode.GetByIndex(index) - } else { - return node.getRightNode().GetByIndex(index - leftNode.size) } + panic("GetByIndex asked for invalid index") + } + // TODO: could improve this by storing the sizes + leftNode := node.getLeftNode() + if index < leftNode.size { + return leftNode.GetByIndex(index) } + return node.getRightNode().GetByIndex(index - leftNode.size) } +// Set inserts a new node with the given key-value pair into the subtree rooted at the node, +// and returns the new root of the subtree and whether an existing node was updated. +// // XXX consider a better way to do this... perhaps split Node from Node. func (node *Node) Set(key string, value interface{}) (newSelf *Node, updated bool) { if node == nil { return NewNode(key, value), false } + if node.height == 0 { - if key < node.key { - return &Node{ - key: node.key, - height: 1, - size: 2, - leftNode: NewNode(key, value), - rightNode: node, - }, false - } else if key == node.key { - return NewNode(key, value), true - } else { - return &Node{ - key: key, - height: 1, - size: 2, - leftNode: node, - rightNode: NewNode(key, value), - }, false - } + return node.setLeaf(key, value) + } + + node = node._copy() + if key < node.key { + node.leftNode, updated = node.getLeftNode().Set(key, value) } else { - node = node._copy() - if key < node.key { - node.leftNode, updated = node.getLeftNode().Set(key, value) - } else { - node.rightNode, updated = node.getRightNode().Set(key, value) - } - if updated { - return node, updated - } else { - node.calcHeightAndSize() - return node.balance(), updated - } + node.rightNode, updated = node.getRightNode().Set(key, value) + } + + if updated { + return node, updated } + + node.calcHeightAndSize() + return node.balance(), updated +} + +// setLeaf inserts a new leaf node with the given key-value pair into the subtree rooted at the node, +// and returns the new root of the subtree and whether an existing node was updated. +func (node *Node) setLeaf(key string, value interface{}) (newSelf *Node, updated bool) { + if key == node.key { + return NewNode(key, value), true + } + + if key < node.key { + return &Node{ + key: node.key, + height: 1, + size: 2, + leftNode: NewNode(key, value), + rightNode: node, + }, false + } + + return &Node{ + key: key, + height: 1, + size: 2, + leftNode: node, + rightNode: NewNode(key, value), + }, false } -// newNode: The new node to replace node after remove. -// newKey: new leftmost leaf key for node after successfully removing 'key' if changed. -// value: removed value. +// Remove deletes the node with the given key from the subtree rooted at the node. +// returns the new root of the subtree, the new leftmost leaf key (if changed), +// the removed value and the removal was successful. func (node *Node) Remove(key string) ( newNode *Node, newKey string, value interface{}, removed bool, ) { @@ -167,51 +187,54 @@ func (node *Node) Remove(key string) ( if node.height == 0 { if key == node.key { return nil, "", node.value, true - } else { - return node, "", nil, false } - } else { - if key < node.key { - var newLeftNode *Node - newLeftNode, newKey, value, removed = node.getLeftNode().Remove(key) - if !removed { - return node, "", value, false - } else if newLeftNode == nil { // left node held value, was removed - return node.rightNode, node.key, value, true - } - node = node._copy() - node.leftNode = newLeftNode - node.calcHeightAndSize() - node = node.balance() - return node, newKey, value, true - } else { - var newRightNode *Node - newRightNode, newKey, value, removed = node.getRightNode().Remove(key) - if !removed { - return node, "", value, false - } else if newRightNode == nil { // right node held value, was removed - return node.leftNode, "", value, true - } - node = node._copy() - node.rightNode = newRightNode - if newKey != "" { - node.key = newKey - } - node.calcHeightAndSize() - node = node.balance() - return node, "", value, true + return node, "", nil, false + } + if key < node.key { + var newLeftNode *Node + newLeftNode, newKey, value, removed = node.getLeftNode().Remove(key) + if !removed { + return node, "", value, false + } + if newLeftNode == nil { // left node held value, was removed + return node.rightNode, node.key, value, true } + node = node._copy() + node.leftNode = newLeftNode + node.calcHeightAndSize() + node = node.balance() + return node, newKey, value, true + } + + var newRightNode *Node + newRightNode, newKey, value, removed = node.getRightNode().Remove(key) + if !removed { + return node, "", value, false + } + if newRightNode == nil { // right node held value, was removed + return node.leftNode, "", value, true } + node = node._copy() + node.rightNode = newRightNode + if newKey != "" { + node.key = newKey + } + node.calcHeightAndSize() + node = node.balance() + return node, "", value, true } +// getLeftNode returns the left child of the node. func (node *Node) getLeftNode() *Node { return node.leftNode } +// getRightNode returns the right child of the node. func (node *Node) getRightNode() *Node { return node.rightNode } +// rotateRight performs a right rotation on the node and returns the new root. // NOTE: overwrites node // TODO: optimize balance & rotate func (node *Node) rotateRight() *Node { @@ -229,6 +252,7 @@ func (node *Node) rotateRight() *Node { return _l } +// rotateLeft performs a left rotation on the node and returns the new root. // NOTE: overwrites node // TODO: optimize balance & rotate func (node *Node) rotateLeft() *Node { @@ -246,48 +270,46 @@ func (node *Node) rotateLeft() *Node { return _r } +// calcHeightAndSize updates the height and size of the node based on its children. // NOTE: mutates height and size func (node *Node) calcHeightAndSize() { node.height = maxInt8(node.getLeftNode().height, node.getRightNode().height) + 1 node.size = node.getLeftNode().size + node.getRightNode().size } +// calcBalance calculates the balance factor of the node. func (node *Node) calcBalance() int { return int(node.getLeftNode().height) - int(node.getRightNode().height) } +// balance balances the subtree rooted at the node and returns the new root. // NOTE: assumes that node can be modified // TODO: optimize balance & rotate func (node *Node) balance() (newSelf *Node) { balance := node.calcBalance() + if balance >= -1 { + return node + } if balance > 1 { if node.getLeftNode().calcBalance() >= 0 { // Left Left Case return node.rotateRight() - } else { - // Left Right Case - // node = node._copy() - left := node.getLeftNode() - node.leftNode = left.rotateLeft() - // node.calcHeightAndSize() - return node.rotateRight() } + // Left Right Case + left := node.getLeftNode() + node.leftNode = left.rotateLeft() + return node.rotateRight() } - if balance < -1 { - if node.getRightNode().calcBalance() <= 0 { - // Right Right Case - return node.rotateLeft() - } else { - // Right Left Case - // node = node._copy() - right := node.getRightNode() - node.rightNode = right.rotateRight() - // node.calcHeightAndSize() - return node.rotateLeft() - } + + if node.getRightNode().calcBalance() <= 0 { + // Right Right Case + return node.rotateLeft() } - // Nothing changed - return node + + // Right Left Case + right := node.getRightNode() + node.rightNode = right.rotateRight() + return node.rotateLeft() } // Shortcut for TraverseInRange. @@ -382,9 +404,11 @@ func (node *Node) TraverseByOffset(offset, limit int, descending bool, leavesOnl return node.traverseByOffset(offset, limit, descending, leavesOnly, cb) } +// TraverseByOffset traverses the subtree rooted at the node by offset and limit, +// in either ascending or descending order, and applies the callback function to each traversed node. +// If leavesOnly is true, only leaf nodes are visited. func (node *Node) traverseByOffset(offset, limit int, descending bool, leavesOnly bool, cb func(*Node) bool) bool { // caller guarantees: offset < node.size; limit > 0. - if !leavesOnly { if cb(node) { return true diff --git a/examples/gno.land/p/demo/avl/node_test.gno b/examples/gno.land/p/demo/avl/node_test.gno index a42cb3a97a8..f24217625ea 100644 --- a/examples/gno.land/p/demo/avl/node_test.gno +++ b/examples/gno.land/p/demo/avl/node_test.gno @@ -23,15 +23,15 @@ Browser` {"descending", true}, } - for _, tc := range tt { - t.Run(tc.name, func(t *testing.T) { + for _, tt := range tt { + t.Run(tt.name, func(t *testing.T) { sl := strings.Split(testStrings, "\n") // sort a first time in the order opposite to how we'll be traversing // the tree, to ensure that we are not just iterating through with // insertion order. - sort.Sort(sort.StringSlice(sl)) - if !tc.desc { + sort.Strings(sl) + if !tt.desc { reverseSlice(sl) } @@ -46,7 +46,7 @@ Browser` var result []string for i := 0; i < len(sl); i++ { - r.TraverseByOffset(i, 1, tc.desc, true, func(n *Node) bool { + r.TraverseByOffset(i, 1, tt.desc, true, func(n *Node) bool { result = append(result, n.Key()) return false }) @@ -66,11 +66,10 @@ Browser` exp := sl[i:max] actual := []string{} - r.TraverseByOffset(i, l, tc.desc, true, func(tr *Node) bool { + r.TraverseByOffset(i, l, tt.desc, true, func(tr *Node) bool { actual = append(actual, tr.Key()) return false }) - // t.Log(exp, actual) if !slicesEqual(exp, actual) { t.Errorf("want %v got %v", exp, actual) } @@ -80,6 +79,455 @@ Browser` } } +func TestHas(t *testing.T) { + tests := []struct { + name string + input []string + hasKey string + expected bool + }{ + { + "has key in non-empty tree", + []string{"C", "A", "B", "E", "D"}, + "B", + true, + }, + { + "does not have key in non-empty tree", + []string{"C", "A", "B", "E", "D"}, + "F", + false, + }, + { + "has key in single-node tree", + []string{"A"}, + "A", + true, + }, + { + "does not have key in single-node tree", + []string{"A"}, + "B", + false, + }, + { + "does not have key in empty tree", + []string{}, + "A", + false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var tree *Node + for _, key := range tt.input { + tree, _ = tree.Set(key, nil) + } + + result := tree.Has(tt.hasKey) + + if result != tt.expected { + t.Errorf("Expected %v, got %v", tt.expected, result) + } + }) + } +} + +func TestGet(t *testing.T) { + tests := []struct { + name string + input []string + getKey string + expectIdx int + expectVal interface{} + expectExists bool + }{ + { + "get existing key", + []string{"C", "A", "B", "E", "D"}, + "B", + 1, + nil, + true, + }, + { + "get non-existent key (smaller)", + []string{"C", "A", "B", "E", "D"}, + "@", + 0, + nil, + false, + }, + { + "get non-existent key (larger)", + []string{"C", "A", "B", "E", "D"}, + "F", + 5, + nil, + false, + }, + { + "get from empty tree", + []string{}, + "A", + 0, + nil, + false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var tree *Node + for _, key := range tt.input { + tree, _ = tree.Set(key, nil) + } + + idx, val, exists := tree.Get(tt.getKey) + + if idx != tt.expectIdx { + t.Errorf("Expected index %d, got %d", tt.expectIdx, idx) + } + + if val != tt.expectVal { + t.Errorf("Expected value %v, got %v", tt.expectVal, val) + } + + if exists != tt.expectExists { + t.Errorf("Expected exists %t, got %t", tt.expectExists, exists) + } + }) + } +} + +func TestGetByIndex(t *testing.T) { + tests := []struct { + name string + input []string + idx int + expectKey string + expectVal interface{} + expectPanic bool + }{ + { + "get by valid index", + []string{"C", "A", "B", "E", "D"}, + 2, + "C", + nil, + false, + }, + { + "get by valid index (smallest)", + []string{"C", "A", "B", "E", "D"}, + 0, + "A", + nil, + false, + }, + { + "get by valid index (largest)", + []string{"C", "A", "B", "E", "D"}, + 4, + "E", + nil, + false, + }, + { + "get by invalid index (negative)", + []string{"C", "A", "B", "E", "D"}, + -1, + "", + nil, + true, + }, + { + "get by invalid index (out of range)", + []string{"C", "A", "B", "E", "D"}, + 5, + "", + nil, + true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var tree *Node + for _, key := range tt.input { + tree, _ = tree.Set(key, nil) + } + + if tt.expectPanic { + defer func() { + if r := recover(); r == nil { + t.Errorf("Expected a panic but didn't get one") + } + }() + } + + key, val := tree.GetByIndex(tt.idx) + + if !tt.expectPanic { + if key != tt.expectKey { + t.Errorf("Expected key %s, got %s", tt.expectKey, key) + } + + if val != tt.expectVal { + t.Errorf("Expected value %v, got %v", tt.expectVal, val) + } + } + }) + } +} + +func TestRemove(t *testing.T) { + tests := []struct { + name string + input []string + removeKey string + expected []string + }{ + { + "remove leaf node", + []string{"C", "A", "B", "D"}, + "B", + []string{"A", "C", "D"}, + }, + { + "remove node with one child", + []string{"C", "A", "B", "D"}, + "A", + []string{"B", "C", "D"}, + }, + { + "remove node with two children", + []string{"C", "A", "B", "E", "D"}, + "C", + []string{"A", "B", "D", "E"}, + }, + { + "remove root node", + []string{"C", "A", "B", "E", "D"}, + "C", + []string{"A", "B", "D", "E"}, + }, + { + "remove non-existent key", + []string{"C", "A", "B", "E", "D"}, + "F", + []string{"A", "B", "C", "D", "E"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var tree *Node + for _, key := range tt.input { + tree, _ = tree.Set(key, nil) + } + + tree, _, _, _ = tree.Remove(tt.removeKey) + + result := make([]string, 0) + tree.Iterate("", "", func(n *Node) bool { + result = append(result, n.Key()) + return false + }) + + if !slicesEqual(tt.expected, result) { + t.Errorf("want %v got %v", tt.expected, result) + } + }) + } +} + +func TestTraverse(t *testing.T) { + tests := []struct { + name string + input []string + expected []string + }{ + { + "empty tree", + []string{}, + []string{}, + }, + { + "single node tree", + []string{"A"}, + []string{"A"}, + }, + { + "small tree", + []string{"C", "A", "B", "E", "D"}, + []string{"A", "B", "C", "D", "E"}, + }, + { + "large tree", + []string{"H", "D", "L", "B", "F", "J", "N", "A", "C", "E", "G", "I", "K", "M", "O"}, + []string{"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var tree *Node + for _, key := range tt.input { + tree, _ = tree.Set(key, nil) + } + + t.Run("iterate", func(t *testing.T) { + var result []string + tree.Iterate("", "", func(n *Node) bool { + result = append(result, n.Key()) + return false + }) + if !slicesEqual(tt.expected, result) { + t.Errorf("want %v got %v", tt.expected, result) + } + }) + + t.Run("ReverseIterate", func(t *testing.T) { + var result []string + tree.ReverseIterate("", "", func(n *Node) bool { + result = append(result, n.Key()) + return false + }) + expected := make([]string, len(tt.expected)) + copy(expected, tt.expected) + for i, j := 0, len(expected)-1; i < j; i, j = i+1, j-1 { + expected[i], expected[j] = expected[j], expected[i] + } + if !slicesEqual(expected, result) { + t.Errorf("want %v got %v", expected, result) + } + }) + + t.Run("TraverseInRange", func(t *testing.T) { + var result []string + start, end := "C", "M" + tree.TraverseInRange(start, end, true, true, func(n *Node) bool { + result = append(result, n.Key()) + return false + }) + expected := make([]string, 0) + for _, key := range tt.expected { + if key >= start && key < end { + expected = append(expected, key) + } + } + if !slicesEqual(expected, result) { + t.Errorf("want %v got %v", expected, result) + } + }) + }) + } +} + +func TestRotateWhenHeightDiffers(t *testing.T) { + tests := []struct { + name string + input []string + expected []string + }{ + { + "right rotation when left subtree is higher", + []string{"E", "C", "A", "B", "D"}, + []string{"A", "B", "C", "E", "D"}, + }, + { + "left rotation when right subtree is higher", + []string{"A", "C", "E", "D", "F"}, + []string{"A", "C", "D", "E", "F"}, + }, + { + "left-right rotation", + []string{"E", "A", "C", "B", "D"}, + []string{"A", "B", "C", "E", "D"}, + }, + { + "right-left rotation", + []string{"A", "E", "C", "B", "D"}, + []string{"A", "B", "C", "E", "D"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var tree *Node + for _, key := range tt.input { + tree, _ = tree.Set(key, nil) + } + + // perform rotation or balance + tree = tree.balance() + + // check tree structure + var result []string + tree.Iterate("", "", func(n *Node) bool { + result = append(result, n.Key()) + return false + }) + + if !slicesEqual(tt.expected, result) { + t.Errorf("want %v got %v", tt.expected, result) + } + }) + } +} + +func TestRotateAndBalance(t *testing.T) { + tests := []struct { + name string + input []string + expected []string + }{ + { + "right rotation", + []string{"A", "B", "C", "D", "E"}, + []string{"A", "B", "C", "D", "E"}, + }, + { + "left rotation", + []string{"E", "D", "C", "B", "A"}, + []string{"A", "B", "C", "D", "E"}, + }, + { + "left-right rotation", + []string{"C", "A", "E", "B", "D"}, + []string{"A", "B", "C", "D", "E"}, + }, + { + "right-left rotation", + []string{"C", "E", "A", "D", "B"}, + []string{"A", "B", "C", "D", "E"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var tree *Node + for _, key := range tt.input { + tree, _ = tree.Set(key, nil) + } + + tree = tree.balance() + + var result []string + tree.Iterate("", "", func(n *Node) bool { + result = append(result, n.Key()) + return false + }) + + if !slicesEqual(tt.expected, result) { + t.Errorf("want %v got %v", tt.expected, result) + } + }) + } +} + func slicesEqual(w1, w2 []string) bool { if len(w1) != len(w2) { return false @@ -92,6 +540,13 @@ func slicesEqual(w1, w2 []string) bool { return true } +func maxint8(a, b int8) int8 { + if a > b { + return a + } + return b +} + func reverseSlice(ss []string) { for i := 0; i < len(ss)/2; i++ { j := len(ss) - 1 - i diff --git a/examples/gno.land/p/demo/avl/tree.gno b/examples/gno.land/p/demo/avl/tree.gno index 7b33d28fbe3..e7aa55eb7e4 100644 --- a/examples/gno.land/p/demo/avl/tree.gno +++ b/examples/gno.land/p/demo/avl/tree.gno @@ -10,42 +10,57 @@ type Tree struct { node *Node } +// NewTree creates a new empty AVL tree. func NewTree() *Tree { return &Tree{ node: nil, } } +// Size returns the number of key-value pair in the tree. func (tree *Tree) Size() int { return tree.node.Size() } +// Has checks whether a key exists in the tree. +// It returns true if the key exists, otherwise false. func (tree *Tree) Has(key string) (has bool) { return tree.node.Has(key) } +// Get retrieves the value associated with the given key. +// It returns the value and a boolean indicating whether the key exists. func (tree *Tree) Get(key string) (value interface{}, exists bool) { _, value, exists = tree.node.Get(key) return } +// GetByIndex retrieves the key-value pair at the specified index in the tree. +// It returns the key and value at the given index. func (tree *Tree) GetByIndex(index int) (key string, value interface{}) { return tree.node.GetByIndex(index) } +// Set inserts a key-value pair into the tree. +// If the key already exists, the value will be updated. +// It returns a boolean indicating whether the key was newly inserted or updated. func (tree *Tree) Set(key string, value interface{}) (updated bool) { newnode, updated := tree.node.Set(key, value) tree.node = newnode return updated } +// Remove removes a key-value pair from the tree. +// It returns the removed value and a boolean indicating whether the key was found and removed. func (tree *Tree) Remove(key string) (value interface{}, removed bool) { newnode, _, value, removed := tree.node.Remove(key) tree.node = newnode return value, removed } -// Shortcut for TraverseInRange. +// Iterate performs an in-order traversal of the tree within the specified key range. +// It calls the provided callback function for each key-value pair encountered. +// If the callback returns true, the iteration is stopped. func (tree *Tree) Iterate(start, end string, cb IterCbFn) bool { return tree.node.TraverseInRange(start, end, true, true, func(node *Node) bool { @@ -54,7 +69,9 @@ func (tree *Tree) Iterate(start, end string, cb IterCbFn) bool { ) } -// Shortcut for TraverseInRange. +// ReverseIterate performs a reverse in-order traversal of the tree within the specified key range. +// It calls the provided callback function for each key-value pair encountered. +// If the callback returns true, the iteration is stopped. func (tree *Tree) ReverseIterate(start, end string, cb IterCbFn) bool { return tree.node.TraverseInRange(start, end, false, true, func(node *Node) bool { @@ -63,7 +80,9 @@ func (tree *Tree) ReverseIterate(start, end string, cb IterCbFn) bool { ) } -// Shortcut for TraverseByOffset. +// IterateByOffset performs an in-order traversal of the tree starting from the specified offset. +// It calls the provided callback function for each key-value pair encountered, up to the specified count. +// If the callback returns true, the iteration is stopped. func (tree *Tree) IterateByOffset(offset int, count int, cb IterCbFn) bool { return tree.node.TraverseByOffset(offset, count, true, true, func(node *Node) bool { @@ -72,7 +91,9 @@ func (tree *Tree) IterateByOffset(offset int, count int, cb IterCbFn) bool { ) } -// Shortcut for TraverseByOffset. +// ReverseIterateByOffset performs a reverse in-order traversal of the tree starting from the specified offset. +// It calls the provided callback function for each key-value pair encountered, up to the specified count. +// If the callback returns true, the iteration is stopped. func (tree *Tree) ReverseIterateByOffset(offset int, count int, cb IterCbFn) bool { return tree.node.TraverseByOffset(offset, count, false, true, func(node *Node) bool { diff --git a/examples/gno.land/p/demo/avl/tree_test.gno b/examples/gno.land/p/demo/avl/tree_test.gno new file mode 100644 index 00000000000..76b108933c6 --- /dev/null +++ b/examples/gno.land/p/demo/avl/tree_test.gno @@ -0,0 +1,163 @@ +package avl + +import ( + "testing" +) + +func TestNewTree(t *testing.T) { + tree := NewTree() + if tree.node != nil { + t.Error("Expected tree.node to be nil") + } +} + +func TestTreeSize(t *testing.T) { + tree := NewTree() + if tree.Size() != 0 { + t.Error("Expected empty tree size to be 0") + } + + tree.Set("key1", "value1") + tree.Set("key2", "value2") + if tree.Size() != 2 { + t.Error("Expected tree size to be 2") + } +} + +func TestTreeHas(t *testing.T) { + tree := NewTree() + tree.Set("key1", "value1") + + if !tree.Has("key1") { + t.Error("Expected tree to have key1") + } + + if tree.Has("key2") { + t.Error("Expected tree to not have key2") + } +} + +func TestTreeGet(t *testing.T) { + tree := NewTree() + tree.Set("key1", "value1") + + value, exists := tree.Get("key1") + if !exists || value != "value1" { + t.Error("Expected Get to return value1 and true") + } + + _, exists = tree.Get("key2") + if exists { + t.Error("Expected Get to return false for non-existent key") + } +} + +func TestTreeGetByIndex(t *testing.T) { + tree := NewTree() + tree.Set("key1", "value1") + tree.Set("key2", "value2") + + key, value := tree.GetByIndex(0) + if key != "key1" || value != "value1" { + t.Error("Expected GetByIndex(0) to return key1 and value1") + } + + key, value = tree.GetByIndex(1) + if key != "key2" || value != "value2" { + t.Error("Expected GetByIndex(1) to return key2 and value2") + } + + defer func() { + if r := recover(); r == nil { + t.Error("Expected GetByIndex to panic for out-of-range index") + } + }() + tree.GetByIndex(2) +} + +func TestTreeRemove(t *testing.T) { + tree := NewTree() + tree.Set("key1", "value1") + + value, removed := tree.Remove("key1") + if !removed || value != "value1" || tree.Size() != 0 { + t.Error("Expected Remove to remove key-value pair") + } + + _, removed = tree.Remove("key2") + if removed { + t.Error("Expected Remove to return false for non-existent key") + } +} + +func TestTreeIterate(t *testing.T) { + tree := NewTree() + tree.Set("key1", "value1") + tree.Set("key2", "value2") + tree.Set("key3", "value3") + + var keys []string + tree.Iterate("", "", func(key string, value interface{}) bool { + keys = append(keys, key) + return false + }) + + expectedKeys := []string{"key1", "key2", "key3"} + if !slicesEqual(keys, expectedKeys) { + t.Errorf("Expected keys %v, got %v", expectedKeys, keys) + } +} + +func TestTreeReverseIterate(t *testing.T) { + tree := NewTree() + tree.Set("key1", "value1") + tree.Set("key2", "value2") + tree.Set("key3", "value3") + + var keys []string + tree.ReverseIterate("", "", func(key string, value interface{}) bool { + keys = append(keys, key) + return false + }) + + expectedKeys := []string{"key3", "key2", "key1"} + if !slicesEqual(keys, expectedKeys) { + t.Errorf("Expected keys %v, got %v", expectedKeys, keys) + } +} + +func TestTreeIterateByOffset(t *testing.T) { + tree := NewTree() + tree.Set("key1", "value1") + tree.Set("key2", "value2") + tree.Set("key3", "value3") + + var keys []string + tree.IterateByOffset(1, 2, func(key string, value interface{}) bool { + keys = append(keys, key) + return false + }) + + expectedKeys := []string{"key2", "key3"} + if !slicesEqual(keys, expectedKeys) { + t.Errorf("Expected keys %v, got %v", expectedKeys, keys) + } +} + +func TestTreeReverseIterateByOffset(t *testing.T) { + tree := NewTree() + tree.Set("key1", "value1") + tree.Set("key2", "value2") + tree.Set("key3", "value3") + + var keys []string + tree.ReverseIterateByOffset(1, 2, func(key string, value interface{}) bool { + keys = append(keys, key) + return false + }) + + expectedKeys := []string{"key2", "key1"} + if !slicesEqual(keys, expectedKeys) { + t.Errorf("Expected keys %v, got %v", expectedKeys, keys) + } +}