Skip to content

Commit

Permalink
feat(examples): add moul/ulist/lplist
Browse files Browse the repository at this point in the history
Signed-off-by: moul <[email protected]>
  • Loading branch information
moul committed Dec 27, 2024
1 parent 83c8a70 commit 97e2c9c
Show file tree
Hide file tree
Showing 3 changed files with 363 additions and 0 deletions.
1 change: 1 addition & 0 deletions examples/gno.land/p/moul/ulist/lplist/gno.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
gno.land/p/moul/ulist/lplist
201 changes: 201 additions & 0 deletions examples/gno.land/p/moul/ulist/lplist/layered_proxy_list.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
package ulist

import (
"errors"
)

// MigratorFn is a function type that lazily converts values from source to target
type MigratorFn func(interface{}) interface{}

// LayeredProxyList represents a wrapper around an existing List that handles migration
type LayeredProxyList struct {
source IList
target *List
migrator MigratorFn
sourceHeight int // Store initial source size to optimize lookups
}

// NewLayeredProxyList creates a new LayeredProxyList instance that wraps an existing List
func NewLayeredProxyList(source IList, migrator MigratorFn) *LayeredProxyList {
sourceHeight := source.TotalSize()
target := New()
target.totalSize = sourceHeight
return &LayeredProxyList{
source: source,
target: target,
migrator: migrator,
sourceHeight: sourceHeight,
}
}

// Get retrieves the value at the specified index
// Uses sourceHeight to efficiently route requests
func (l *LayeredProxyList) Get(index int) interface{} {
if index < l.sourceHeight {
// Direct access to source for indices below sourceHeight
val := l.source.Get(index)
if val == nil {
return nil
}
// Only apply migrator if it exists
if l.migrator != nil {
return l.migrator(val)
}
return val
}
// Access target list directly for new indices
return l.target.Get(index)
}

// Append adds one or more values to the target list
func (l *LayeredProxyList) Append(values ...interface{}) {
l.target.Append(values...)
}

// Delete marks elements as deleted in the appropriate list
func (l *LayeredProxyList) Delete(indices ...int) error {
for _, index := range indices {
if index < l.sourceHeight {
return errors.New("cannot delete from source list")
}
}
return l.target.Delete(indices...)
}

// Size returns the total number of active elements
func (l *LayeredProxyList) Size() int {
return l.source.Size() + l.target.Size()
}

// TotalSize returns the total number of elements ever added
func (l *LayeredProxyList) TotalSize() int {
return l.target.TotalSize()
}

// MustDelete deletes elements, panicking on error
func (l *LayeredProxyList) MustDelete(indices ...int) {
if err := l.Delete(indices...); err != nil {
panic(err)
}
}

// MustGet retrieves a value, panicking if not found
func (l *LayeredProxyList) MustGet(index int) interface{} {
val := l.Get(index)
if val == nil {
panic(ErrDeleted)
}
return val
}

// GetRange returns elements between start and end indices
func (l *LayeredProxyList) GetRange(start, end int) []Entry {
var entries []Entry
l.Iterator(start, end, func(index int, value interface{}) bool {
entries = append(entries, Entry{Index: index, Value: value})
return false
})
return entries
}

// GetRangeByOffset returns elements starting from offset
func (l *LayeredProxyList) GetRangeByOffset(offset int, count int) []Entry {
var entries []Entry
l.IteratorByOffset(offset, count, func(index int, value interface{}) bool {
entries = append(entries, Entry{Index: index, Value: value})
return false
})
return entries
}

// Iterator performs iteration between start and end indices
func (l *LayeredProxyList) Iterator(start, end int, cb IterCbFn) bool {
// For empty list or invalid range
if start < 0 && end < 0 {
return false
}

// Normalize indices
if start < 0 {
start = 0
}
if end < 0 {
end = 0
}

totalSize := l.TotalSize()
if end >= totalSize {
end = totalSize - 1
}
if start >= totalSize {
start = totalSize - 1
}

// Handle reverse iteration
if start > end {
for i := start; i >= end; i-- {
val := l.Get(i)
if val != nil {
if cb(i, val) {
return true
}
}
}
return false
}

// Handle forward iteration
for i := start; i <= end; i++ {
val := l.Get(i)
if val != nil {
if cb(i, val) {
return true
}
}
}
return false
}

// IteratorByOffset performs iteration starting from offset
func (l *LayeredProxyList) IteratorByOffset(offset int, count int, cb IterCbFn) bool {
if count == 0 {
return false
}

// Normalize offset
if offset < 0 {
offset = 0
}
totalSize := l.TotalSize()
if offset >= totalSize {
offset = totalSize - 1
}

// Determine end based on count direction
var end int
if count > 0 {
end = totalSize - 1
} else {
end = 0
}

wrapperReturned := false
remaining := abs(count)
wrapper := func(index int, value interface{}) bool {
if remaining <= 0 {
wrapperReturned = true
return true
}
remaining--
return cb(index, value)
}

ret := l.Iterator(offset, end, wrapper)
if wrapperReturned {
return false
}
return ret
}

// Verify that LayeredProxyList implements IList
var _ IList = (*LayeredProxyList)(nil)
161 changes: 161 additions & 0 deletions examples/gno.land/p/moul/ulist/lplist/layered_proxy_list_test.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
package ulist

import (
"testing"
)

// TestLayeredProxyListBasicOperations tests the basic operations of LayeredProxyList
func TestLayeredProxyListBasicOperations(t *testing.T) {
// Create source list with initial data
source := New()
source.Append(1, 2, 3)

// Create proxy list with a simple multiplier migrator
migrator := func(v interface{}) interface{} {
return v.(int) * 2
}
proxy := NewLayeredProxyList(source, migrator)

// Test initial state
if got := proxy.Size(); got != 3 {
t.Errorf("initial Size() = %v, want %v", got, 3)
}
if got := proxy.TotalSize(); got != 3 {
t.Errorf("initial TotalSize() = %v, want %v", got, 3)
}

// Test Get with migration
tests := []struct {
index int
want interface{}
}{
{0, 2}, // 1 * 2
{1, 4}, // 2 * 2
{2, 6}, // 3 * 2
}

for _, tt := range tests {
if got := proxy.Get(tt.index); got != tt.want {
t.Errorf("Get(%v) = %v, want %v", tt.index, got, tt.want)
}
}

// Test Append to target
proxy.Append(7, 8)
if got := proxy.Size(); got != 5 {
t.Errorf("Size() after append = %v, want %v", got, 5)
}

// Test Get from target (no migration)
if got := proxy.Get(3); got != 7 {
t.Errorf("Get(3) = %v, want %v", got, 7)
}
}

// TestLayeredProxyListDelete tests delete operations
func TestLayeredProxyListDelete(t *testing.T) {
source := New()
source.Append(1, 2, 3)
proxy := NewLayeredProxyList(source, nil)
proxy.Append(4, 5)

// Test delete from source (should fail)
if err := proxy.Delete(1); err == nil {
t.Error("Delete from source should return error")
}

// Test delete from target (should succeed)
if err := proxy.Delete(3); err != nil {
t.Errorf("Delete from target failed: %v", err)
}

// Verify deletion
if got := proxy.Get(3); got != nil {
t.Errorf("Get(3) after delete = %v, want nil", got)
}
}

// TestLayeredProxyListIteration tests iteration methods
func TestLayeredProxyListIteration(t *testing.T) {
source := New()
source.Append(1, 2, 3)
proxy := NewLayeredProxyList(source, nil)
proxy.Append(4, 5)

// Test GetRange
entries := proxy.GetRange(0, 4)
if len(entries) != 5 {
t.Errorf("GetRange returned %v entries, want 5", len(entries))
}

// Test reverse iteration
entries = proxy.GetRange(4, 0)
if len(entries) != 5 {
t.Errorf("Reverse GetRange returned %v entries, want 5", len(entries))
}

// Test IteratorByOffset with positive count
var values []interface{}
proxy.IteratorByOffset(1, 3, func(index int, value interface{}) bool {
values = append(values, value)
return false
})
if len(values) != 3 {
t.Errorf("IteratorByOffset returned %v values, want 3", len(values))
}
}

// TestLayeredProxyListMustOperations tests must operations
func TestLayeredProxyListMustOperations(t *testing.T) {
source := New()
source.Append(1, 2)
proxy := NewLayeredProxyList(source, nil)

// Test MustGet success
defer func() {
if r := recover(); r != nil {
t.Errorf("MustGet panicked unexpectedly: %v", r)
}
}()
if got := proxy.MustGet(1); got != 2 {
t.Errorf("MustGet(1) = %v, want 2", got)
}

// Test MustGet panic
defer func() {
if r := recover(); r == nil {
t.Error("MustGet should have panicked")
}
}()
proxy.MustGet(99) // Should panic
}

// TestLayeredProxyListWithNilMigrator tests behavior without a migrator
func TestLayeredProxyListWithNilMigrator(t *testing.T) {
source := New()
source.Append(1, 2)
proxy := NewLayeredProxyList(source, nil)

if got := proxy.Get(0); got != 1 {
t.Errorf("Get(0) with nil migrator = %v, want 1", got)
}
}

// TestLayeredProxyListEmpty tests operations on empty lists
func TestLayeredProxyListEmpty(t *testing.T) {
source := New()
proxy := NewLayeredProxyList(source, nil)

if got := proxy.Size(); got != 0 {
t.Errorf("Size() of empty list = %v, want 0", got)
}

if got := proxy.Get(0); got != nil {
t.Errorf("Get(0) of empty list = %v, want nil", got)
}

entries := proxy.GetRange(0, 10)
if len(entries) != 0 {
t.Errorf("GetRange on empty list returned %v entries, want 0", len(entries))
}
}

0 comments on commit 97e2c9c

Please sign in to comment.