From 88fb8cb3aac1e3b393a9f8e88304b78fbe1240da Mon Sep 17 00:00:00 2001 From: Manfred Touron <94029+moul@users.noreply.github.com> Date: Sat, 21 Dec 2024 01:44:14 +0100 Subject: [PATCH] feat(examples): add p/demo/avl/list (#3324) Could become the preferred method for managing top-level lists. It can likely be utilized extensively in the existing codebase, potentially for most instances involving a top-level slice or those involving an avl.Tree using a seqid. --------- Signed-off-by: moul <94029+moul@users.noreply.github.com> Co-authored-by: Morgan --- examples/gno.land/p/demo/avl/list/gno.mod | 1 + examples/gno.land/p/demo/avl/list/list.gno | 298 +++++++++++++ .../gno.land/p/demo/avl/list/list_test.gno | 409 ++++++++++++++++++ 3 files changed, 708 insertions(+) create mode 100644 examples/gno.land/p/demo/avl/list/gno.mod create mode 100644 examples/gno.land/p/demo/avl/list/list.gno create mode 100644 examples/gno.land/p/demo/avl/list/list_test.gno diff --git a/examples/gno.land/p/demo/avl/list/gno.mod b/examples/gno.land/p/demo/avl/list/gno.mod new file mode 100644 index 00000000000..c05923b7708 --- /dev/null +++ b/examples/gno.land/p/demo/avl/list/gno.mod @@ -0,0 +1 @@ +module gno.land/p/demo/avl/list diff --git a/examples/gno.land/p/demo/avl/list/list.gno b/examples/gno.land/p/demo/avl/list/list.gno new file mode 100644 index 00000000000..0875eb66e01 --- /dev/null +++ b/examples/gno.land/p/demo/avl/list/list.gno @@ -0,0 +1,298 @@ +// Package list implements a dynamic list data structure backed by an AVL tree. +// It provides O(log n) operations for most list operations while maintaining +// order stability. +// +// The list supports various operations including append, get, set, delete, +// range queries, and iteration. It can store values of any type. +// +// Example usage: +// +// // Create a new list and add elements +// var l list.List +// l.Append(1, 2, 3) +// +// // Get and set elements +// value := l.Get(1) // returns 2 +// l.Set(1, 42) // updates index 1 to 42 +// +// // Delete elements +// l.Delete(0) // removes first element +// +// // Iterate over elements +// l.ForEach(func(index int, value interface{}) bool { +// ufmt.Printf("index %d: %v\n", index, value) +// return false // continue iteration +// }) +// // Output: +// // index 0: 42 +// // index 1: 3 +// +// // Create a list of specific size +// l = list.Make(3, "default") // creates [default, default, default] +// +// // Create a list using a variable declaration +// var l2 list.List +// l2.Append(4, 5, 6) +// println(l2.Len()) // Output: 3 +package list + +import ( + "gno.land/p/demo/avl" + "gno.land/p/demo/seqid" +) + +// List represents an ordered sequence of items backed by an AVL tree +type List struct { + tree avl.Tree + idGen seqid.ID +} + +// Len returns the number of elements in the list. +// +// Example: +// +// l := list.New() +// l.Append(1, 2, 3) +// println(l.Len()) // Output: 3 +func (l *List) Len() int { + return l.tree.Size() +} + +// Append adds one or more values to the end of the list. +// +// Example: +// +// l := list.New() +// l.Append(1) // adds single value +// l.Append(2, 3, 4) // adds multiple values +// println(l.Len()) // Output: 4 +func (l *List) Append(values ...interface{}) { + for _, v := range values { + l.tree.Set(l.idGen.Next().String(), v) + } +} + +// Get returns the value at the specified index. +// Returns nil if index is out of bounds. +// +// Example: +// +// l := list.New() +// l.Append(1, 2, 3) +// println(l.Get(1)) // Output: 2 +// println(l.Get(-1)) // Output: nil +// println(l.Get(999)) // Output: nil +func (l *List) Get(index int) interface{} { + if index < 0 || index >= l.tree.Size() { + return nil + } + _, value := l.tree.GetByIndex(index) + return value +} + +// Set updates or appends a value at the specified index. +// Returns true if the operation was successful, false otherwise. +// For empty lists, only index 0 is valid (append case). +// +// Example: +// +// l := list.New() +// l.Append(1, 2, 3) +// +// l.Set(1, 42) // updates existing index +// println(l.Get(1)) // Output: 42 +// +// l.Set(3, 4) // appends at end +// println(l.Get(3)) // Output: 4 +// +// l.Set(-1, 5) // invalid index +// println(l.Len()) // Output: 4 (list unchanged) +func (l *List) Set(index int, value interface{}) bool { + size := l.tree.Size() + + // Handle empty list case - only allow index 0 + if size == 0 { + if index == 0 { + l.Append(value) + return true + } + return false + } + + if index < 0 || index > size { + return false + } + + // If setting at the end (append case) + if index == size { + l.Append(value) + return true + } + + // Get the key at the specified index + key, _ := l.tree.GetByIndex(index) + if key == "" { + return false + } + + // Update the value at the existing key + l.tree.Set(key, value) + return true +} + +// Delete removes the element at the specified index. +// Returns the deleted value and true if successful, nil and false otherwise. +// +// Example: +// +// l := list.New() +// l.Append(1, 2, 3) +// +// val, ok := l.Delete(1) +// println(val, ok) // Output: 2 true +// println(l.Len()) // Output: 2 +// +// val, ok = l.Delete(-1) +// println(val, ok) // Output: nil false +func (l *List) Delete(index int) (interface{}, bool) { + size := l.tree.Size() + // Always return nil, false for empty list + if size == 0 { + return nil, false + } + + if index < 0 || index >= size { + return nil, false + } + + key, value := l.tree.GetByIndex(index) + if key == "" { + return nil, false + } + + l.tree.Remove(key) + return value, true +} + +// Slice returns a slice of values from startIndex (inclusive) to endIndex (exclusive). +// Returns nil if the range is invalid. +// +// Example: +// +// l := list.New() +// l.Append(1, 2, 3, 4, 5) +// +// println(l.Slice(1, 4)) // Output: [2 3 4] +// println(l.Slice(-1, 2)) // Output: [1 2] +// println(l.Slice(3, 999)) // Output: [4 5] +// println(l.Slice(3, 2)) // Output: nil +func (l *List) Slice(startIndex, endIndex int) []interface{} { + size := l.tree.Size() + + // Normalize bounds + if startIndex < 0 { + startIndex = 0 + } + if endIndex > size { + endIndex = size + } + if startIndex >= endIndex { + return nil + } + + count := endIndex - startIndex + result := make([]interface{}, count) + + i := 0 + l.tree.IterateByOffset(startIndex, count, func(_ string, value interface{}) bool { + result[i] = value + i++ + return false + }) + return result +} + +// ForEach iterates through all elements in the list. +func (l *List) ForEach(fn func(index int, value interface{}) bool) { + if l.tree.Size() == 0 { + return + } + + index := 0 + l.tree.IterateByOffset(0, l.tree.Size(), func(_ string, value interface{}) bool { + result := fn(index, value) + index++ + return result + }) +} + +// Clone creates a shallow copy of the list. +// +// Example: +// +// l := list.New() +// l.Append(1, 2, 3) +// +// clone := l.Clone() +// clone.Set(0, 42) +// +// println(l.Get(0)) // Output: 1 +// println(clone.Get(0)) // Output: 42 +func (l *List) Clone() *List { + newList := &List{ + tree: avl.Tree{}, + idGen: l.idGen, + } + + size := l.tree.Size() + if size == 0 { + return newList + } + + l.tree.IterateByOffset(0, size, func(_ string, value interface{}) bool { + newList.Append(value) + return false + }) + + return newList +} + +// DeleteRange removes elements from startIndex (inclusive) to endIndex (exclusive). +// Returns the number of elements deleted. +// +// Example: +// +// l := list.New() +// l.Append(1, 2, 3, 4, 5) +// +// deleted := l.DeleteRange(1, 4) +// println(deleted) // Output: 3 +// println(l.Range(0, l.Len())) // Output: [1 5] +func (l *List) DeleteRange(startIndex, endIndex int) int { + size := l.tree.Size() + + // Normalize bounds + if startIndex < 0 { + startIndex = 0 + } + if endIndex > size { + endIndex = size + } + if startIndex >= endIndex { + return 0 + } + + // Collect keys to delete + keysToDelete := make([]string, 0, endIndex-startIndex) + l.tree.IterateByOffset(startIndex, endIndex-startIndex, func(key string, _ interface{}) bool { + keysToDelete = append(keysToDelete, key) + return false + }) + + // Delete collected keys + for _, key := range keysToDelete { + l.tree.Remove(key) + } + + return len(keysToDelete) +} diff --git a/examples/gno.land/p/demo/avl/list/list_test.gno b/examples/gno.land/p/demo/avl/list/list_test.gno new file mode 100644 index 00000000000..265fbdb5eb1 --- /dev/null +++ b/examples/gno.land/p/demo/avl/list/list_test.gno @@ -0,0 +1,409 @@ +package list + +import ( + "testing" +) + +func TestList_Basic(t *testing.T) { + var l List + + // Test empty list + if l.Len() != 0 { + t.Errorf("new list should be empty, got len %d", l.Len()) + } + + // Test append and length + l.Append(1, 2, 3) + if l.Len() != 3 { + t.Errorf("expected len 3, got %d", l.Len()) + } + + // Test get + if v := l.Get(0); v != 1 { + t.Errorf("expected 1 at index 0, got %v", v) + } + if v := l.Get(1); v != 2 { + t.Errorf("expected 2 at index 1, got %v", v) + } + if v := l.Get(2); v != 3 { + t.Errorf("expected 3 at index 2, got %v", v) + } + + // Test out of bounds + if v := l.Get(-1); v != nil { + t.Errorf("expected nil for negative index, got %v", v) + } + if v := l.Get(3); v != nil { + t.Errorf("expected nil for out of bounds index, got %v", v) + } +} + +func TestList_Set(t *testing.T) { + var l List + l.Append(1, 2, 3) + + // Test valid set within bounds + if ok := l.Set(1, 42); !ok { + t.Error("Set should return true for valid index") + } + if v := l.Get(1); v != 42 { + t.Errorf("expected 42 after Set, got %v", v) + } + + // Test set at size (append) + if ok := l.Set(3, 4); !ok { + t.Error("Set should return true when appending at size") + } + if v := l.Get(3); v != 4 { + t.Errorf("expected 4 after Set at size, got %v", v) + } + + // Test invalid sets + if ok := l.Set(-1, 10); ok { + t.Error("Set should return false for negative index") + } + if ok := l.Set(5, 10); ok { + t.Error("Set should return false for index > size") + } + + // Verify list state hasn't changed after invalid operations + expected := []interface{}{1, 42, 3, 4} + for i, want := range expected { + if got := l.Get(i); got != want { + t.Errorf("index %d = %v; want %v", i, got, want) + } + } +} + +func TestList_Delete(t *testing.T) { + var l List + l.Append(1, 2, 3) + + // Test valid delete + if v, ok := l.Delete(1); !ok || v != 2 { + t.Errorf("Delete(1) = %v, %v; want 2, true", v, ok) + } + if l.Len() != 2 { + t.Errorf("expected len 2 after delete, got %d", l.Len()) + } + if v := l.Get(1); v != 3 { + t.Errorf("expected 3 at index 1 after delete, got %v", v) + } + + // Test invalid delete + if v, ok := l.Delete(-1); ok || v != nil { + t.Errorf("Delete(-1) = %v, %v; want nil, false", v, ok) + } + if v, ok := l.Delete(2); ok || v != nil { + t.Errorf("Delete(2) = %v, %v; want nil, false", v, ok) + } +} + +func TestList_Slice(t *testing.T) { + var l List + l.Append(1, 2, 3, 4, 5) + + // Test valid ranges + values := l.Slice(1, 4) + expected := []interface{}{2, 3, 4} + if !sliceEqual(values, expected) { + t.Errorf("Slice(1,4) = %v; want %v", values, expected) + } + + // Test edge cases + if values := l.Slice(-1, 2); !sliceEqual(values, []interface{}{1, 2}) { + t.Errorf("Slice(-1,2) = %v; want [1 2]", values) + } + if values := l.Slice(3, 10); !sliceEqual(values, []interface{}{4, 5}) { + t.Errorf("Slice(3,10) = %v; want [4 5]", values) + } + if values := l.Slice(3, 2); values != nil { + t.Errorf("Slice(3,2) = %v; want nil", values) + } +} + +func TestList_ForEach(t *testing.T) { + var l List + l.Append(1, 2, 3) + + sum := 0 + l.ForEach(func(index int, value interface{}) bool { + sum += value.(int) + return false + }) + + if sum != 6 { + t.Errorf("ForEach sum = %d; want 6", sum) + } + + // Test early termination + count := 0 + l.ForEach(func(index int, value interface{}) bool { + count++ + return true // stop after first item + }) + + if count != 1 { + t.Errorf("ForEach early termination count = %d; want 1", count) + } +} + +func TestList_Clone(t *testing.T) { + var l List + l.Append(1, 2, 3) + + clone := l.Clone() + + // Test same length + if clone.Len() != l.Len() { + t.Errorf("clone.Len() = %d; want %d", clone.Len(), l.Len()) + } + + // Test same values + for i := 0; i < l.Len(); i++ { + if clone.Get(i) != l.Get(i) { + t.Errorf("clone.Get(%d) = %v; want %v", i, clone.Get(i), l.Get(i)) + } + } + + // Test independence + l.Set(0, 42) + if clone.Get(0) == l.Get(0) { + t.Error("clone should be independent of original") + } +} + +func TestList_DeleteRange(t *testing.T) { + var l List + l.Append(1, 2, 3, 4, 5) + + // Test valid range delete + deleted := l.DeleteRange(1, 4) + if deleted != 3 { + t.Errorf("DeleteRange(1,4) deleted %d elements; want 3", deleted) + } + if l.Len() != 2 { + t.Errorf("after DeleteRange(1,4) len = %d; want 2", l.Len()) + } + expected := []interface{}{1, 5} + for i, want := range expected { + if got := l.Get(i); got != want { + t.Errorf("after DeleteRange(1,4) index %d = %v; want %v", i, got, want) + } + } + + // Test edge cases + l = List{} + l.Append(1, 2, 3) + + // Delete with negative start + if deleted := l.DeleteRange(-1, 2); deleted != 2 { + t.Errorf("DeleteRange(-1,2) deleted %d elements; want 2", deleted) + } + + // Delete with end > length + l = List{} + l.Append(1, 2, 3) + if deleted := l.DeleteRange(1, 5); deleted != 2 { + t.Errorf("DeleteRange(1,5) deleted %d elements; want 2", deleted) + } + + // Delete invalid range + if deleted := l.DeleteRange(2, 1); deleted != 0 { + t.Errorf("DeleteRange(2,1) deleted %d elements; want 0", deleted) + } + + // Delete empty range + if deleted := l.DeleteRange(1, 1); deleted != 0 { + t.Errorf("DeleteRange(1,1) deleted %d elements; want 0", deleted) + } +} + +func TestList_EmptyOperations(t *testing.T) { + var l List + + // Operations on empty list + if v := l.Get(0); v != nil { + t.Errorf("Get(0) on empty list = %v; want nil", v) + } + + // Set should work at index 0 for empty list (append case) + if ok := l.Set(0, 1); !ok { + t.Error("Set(0,1) on empty list = false; want true") + } + if v := l.Get(0); v != 1 { + t.Errorf("Get(0) after Set = %v; want 1", v) + } + + l = List{} // Reset to empty list + if v, ok := l.Delete(0); ok || v != nil { + t.Errorf("Delete(0) on empty list = %v, %v; want nil, false", v, ok) + } + if values := l.Slice(0, 1); values != nil { + t.Errorf("Range(0,1) on empty list = %v; want nil", values) + } +} + +func TestList_DifferentTypes(t *testing.T) { + var l List + + // Test with different types + l.Append(42, "hello", true, 3.14) + + if v := l.Get(0).(int); v != 42 { + t.Errorf("Get(0) = %v; want 42", v) + } + if v := l.Get(1).(string); v != "hello" { + t.Errorf("Get(1) = %v; want 'hello'", v) + } + if v := l.Get(2).(bool); !v { + t.Errorf("Get(2) = %v; want true", v) + } + if v := l.Get(3).(float64); v != 3.14 { + t.Errorf("Get(3) = %v; want 3.14", v) + } +} + +func TestList_LargeOperations(t *testing.T) { + var l List + + // Test with larger number of elements + n := 1000 + for i := 0; i < n; i++ { + l.Append(i) + } + + if l.Len() != n { + t.Errorf("Len() = %d; want %d", l.Len(), n) + } + + // Test range on large list + values := l.Slice(n-3, n) + expected := []interface{}{n - 3, n - 2, n - 1} + if !sliceEqual(values, expected) { + t.Errorf("Range(%d,%d) = %v; want %v", n-3, n, values, expected) + } + + // Test large range deletion + deleted := l.DeleteRange(100, 900) + if deleted != 800 { + t.Errorf("DeleteRange(100,900) = %d; want 800", deleted) + } + if l.Len() != 200 { + t.Errorf("Len() after large delete = %d; want 200", l.Len()) + } +} + +func TestList_ChainedOperations(t *testing.T) { + var l List + + // Test sequence of operations + l.Append(1, 2, 3) + l.Delete(1) + l.Append(4) + l.Set(1, 5) + + expected := []interface{}{1, 5, 4} + for i, want := range expected { + if got := l.Get(i); got != want { + t.Errorf("index %d = %v; want %v", i, got, want) + } + } +} + +func TestList_RangeEdgeCases(t *testing.T) { + var l List + l.Append(1, 2, 3, 4, 5) + + // Test various edge cases for Range + cases := []struct { + start, end int + want []interface{} + }{ + {-10, 2, []interface{}{1, 2}}, + {3, 10, []interface{}{4, 5}}, + {0, 0, nil}, + {5, 5, nil}, + {4, 3, nil}, + {-1, -1, nil}, + } + + for _, tc := range cases { + got := l.Slice(tc.start, tc.end) + if !sliceEqual(got, tc.want) { + t.Errorf("Slice(%d,%d) = %v; want %v", tc.start, tc.end, got, tc.want) + } + } +} + +func TestList_IndexConsistency(t *testing.T) { + var l List + + // Initial additions + l.Append(1, 2, 3, 4, 5) // [1,2,3,4,5] + + // Delete from middle + l.Delete(2) // [1,2,4,5] + + // Add more elements + l.Append(6, 7) // [1,2,4,5,6,7] + + // Delete range from middle + l.DeleteRange(1, 4) // [1,6,7] + + // Add more elements + l.Append(8, 9, 10) // [1,6,7,8,9,10] + + // Verify sequence is continuous + expected := []interface{}{1, 6, 7, 8, 9, 10} + for i, want := range expected { + if got := l.Get(i); got != want { + t.Errorf("index %d = %v; want %v", i, got, want) + } + } + + // Verify no extra elements exist + if l.Len() != len(expected) { + t.Errorf("length = %d; want %d", l.Len(), len(expected)) + } + + // Verify all indices are accessible + allValues := l.Slice(0, l.Len()) + if !sliceEqual(allValues, expected) { + t.Errorf("Slice(0, Len()) = %v; want %v", allValues, expected) + } + + // Verify no gaps in iteration + var iteratedValues []interface{} + var indices []int + l.ForEach(func(index int, value interface{}) bool { + iteratedValues = append(iteratedValues, value) + indices = append(indices, index) + return false + }) + + // Check values from iteration + if !sliceEqual(iteratedValues, expected) { + t.Errorf("ForEach values = %v; want %v", iteratedValues, expected) + } + + // Check indices are sequential + for i, idx := range indices { + if idx != i { + t.Errorf("ForEach index %d = %d; want %d", i, idx, i) + } + } +} + +// Helper function to compare slices +func sliceEqual(a, b []interface{}) bool { + if len(a) != len(b) { + return false + } + for i := range a { + if a[i] != b[i] { + return false + } + } + return true +}