-
Notifications
You must be signed in to change notification settings - Fork 399
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(examples): add moul/ulist/lplist
Signed-off-by: moul <[email protected]>
- Loading branch information
Showing
3 changed files
with
363 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
201
examples/gno.land/p/moul/ulist/lplist/layered_proxy_list.gno
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
161
examples/gno.land/p/moul/ulist/lplist/layered_proxy_list_test.gno
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)) | ||
} | ||
} |