diff --git a/examples/gno.land/p/moul/fifo/fifo.gno b/examples/gno.land/p/moul/fifo/fifo.gno new file mode 100644 index 00000000000..9b5013312ca --- /dev/null +++ b/examples/gno.land/p/moul/fifo/fifo.gno @@ -0,0 +1,240 @@ +// Package fifo implements a fixed-size FIFO (First-In-First-Out) list data structure +// using a singly-linked list. The implementation prioritizes storage efficiency by minimizing +// storage operations - each add/remove operation only updates 1-2 pointers, regardless of +// list size. +// +// Key features: +// - Fixed-size with automatic removal of oldest entries when full +// - Support for both prepend (add at start) and append (add at end) operations +// - Constant storage usage through automatic pruning +// - O(1) append operations and latest element access +// - Iterator support for sequential access +// - Dynamic size adjustment via SetMaxSize +// +// This implementation is optimized for frequent updates, as insertions and deletions only +// require updating 1-2 pointers. However, random access operations are O(n) as they require +// traversing the list. For use cases where writes are rare, a slice-based +// implementation might be more suitable. +// +// The linked list structure is equally efficient for storing both small values (like pointers) +// and larger data structures, as each node maintains a single next-pointer regardless of the +// stored value's size. +// +// Example usage: +// +// list := fifo.New(3) // Create a new list with max size 3 +// list.Append("a") // List: [a] +// list.Append("b") // List: [a b] +// list.Append("c") // List: [a b c] +// list.Append("d") // List: [b c d] (oldest element "a" was removed) +// latest := list.Latest() // Returns "d" +// all := list.Entries() // Returns ["b", "c", "d"] +package fifo + +// node represents a single element in the linked list +type node struct { + value interface{} + next *node +} + +// List represents a fixed-size FIFO list +type List struct { + head *node + tail *node + size int + maxSize int +} + +// New creates a new FIFO list with the specified maximum size +func New(maxSize int) *List { + return &List{ + maxSize: maxSize, + } +} + +// Prepend adds a new entry at the start of the list. If the list exceeds maxSize, +// the last entry is automatically removed. +func (l *List) Prepend(entry interface{}) { + if l.maxSize == 0 { + return + } + + newNode := &node{value: entry} + + if l.head == nil { + l.head = newNode + l.tail = newNode + l.size = 1 + return + } + + newNode.next = l.head + l.head = newNode + + if l.size < l.maxSize { + l.size++ + } else { + // Remove last element by traversing to second-to-last + if l.size == 1 { + // Special case: if size is 1, just update both pointers + l.head = newNode + l.tail = newNode + newNode.next = nil + } else { + // Find second-to-last node + current := l.head + for current.next != l.tail { + current = current.next + } + current.next = nil + l.tail = current + } + } +} + +// Append adds a new entry at the end of the list. If the list exceeds maxSize, +// the first entry is automatically removed. +func (l *List) Append(entry interface{}) { + if l.maxSize == 0 { + return + } + + newNode := &node{value: entry} + + if l.head == nil { + l.head = newNode + l.tail = newNode + l.size = 1 + return + } + + l.tail.next = newNode + l.tail = newNode + + if l.size < l.maxSize { + l.size++ + } else { + l.head = l.head.next + } +} + +// Get returns the entry at the specified index. +// Index 0 is the oldest entry, Size()-1 is the newest. +func (l *List) Get(index int) interface{} { + if index < 0 || index >= l.size { + return nil + } + + current := l.head + for i := 0; i < index; i++ { + current = current.next + } + return current.value +} + +// Size returns the current number of entries in the list +func (l *List) Size() int { + return l.size +} + +// MaxSize returns the maximum size configured for this list +func (l *List) MaxSize() int { + return l.maxSize +} + +// Entries returns all current entries as a slice +func (l *List) Entries() []interface{} { + entries := make([]interface{}, l.size) + current := l.head + for i := 0; i < l.size; i++ { + entries[i] = current.value + current = current.next + } + return entries +} + +// Iterator returns a function that can be used to iterate over the entries +// from oldest to newest. Returns nil when there are no more entries. +func (l *List) Iterator() func() interface{} { + current := l.head + return func() interface{} { + if current == nil { + return nil + } + value := current.value + current = current.next + return value + } +} + +// Latest returns the most recent entry. +// Returns nil if the list is empty. +func (l *List) Latest() interface{} { + if l.tail == nil { + return nil + } + return l.tail.value +} + +// SetMaxSize updates the maximum size of the list. +// If the new maxSize is smaller than the current size, +// the oldest entries are removed to fit the new size. +func (l *List) SetMaxSize(maxSize int) { + if maxSize < 0 { + maxSize = 0 + } + + // If new maxSize is smaller than current size, + // remove oldest entries until we fit + if maxSize < l.size { + // Special case: if new maxSize is 0, clear the list + if maxSize == 0 { + l.head = nil + l.tail = nil + l.size = 0 + } else { + // Keep the newest entries by moving head forward + diff := l.size - maxSize + for i := 0; i < diff; i++ { + l.head = l.head.next + } + l.size = maxSize + } + } + + l.maxSize = maxSize +} + +// Delete removes the element at the specified index. +// Returns true if an element was removed, false if the index was invalid. +func (l *List) Delete(index int) bool { + if index < 0 || index >= l.size { + return false + } + + if index == 0 { + // Remove from head + l.head = l.head.next + if l.size == 1 { + l.tail = nil + } + } else { + // Find the node before the one to delete + current := l.head + for i := 0; i < index-1; i++ { + current = current.next + } + + // Skip the node to delete + if index == l.size-1 { + // If deleting last element, update tail + l.tail = current + current.next = nil + } else { + current.next = current.next.next + } + } + + l.size-- + return true +} diff --git a/examples/gno.land/p/moul/fifo/fifo_test.gno b/examples/gno.land/p/moul/fifo/fifo_test.gno new file mode 100644 index 00000000000..1e3d27509c1 --- /dev/null +++ b/examples/gno.land/p/moul/fifo/fifo_test.gno @@ -0,0 +1,294 @@ +package fifo + +import ( + "testing" + + "gno.land/p/demo/uassert" +) + +func TestNew(t *testing.T) { + l := New(5) + uassert.Equal(t, 5, l.MaxSize()) + uassert.Equal(t, 0, l.Size()) +} + +func TestAppend(t *testing.T) { + l := New(3) + + // Test adding within capacity + l.Append(1) + l.Append(2) + uassert.Equal(t, 2, l.Size()) + uassert.Equal(t, 1, l.Get(0)) + uassert.Equal(t, 2, l.Get(1)) + + // Test overflow behavior + l.Append(3) + l.Append(4) + uassert.Equal(t, 3, l.Size()) + uassert.Equal(t, 2, l.Get(0)) + uassert.Equal(t, 3, l.Get(1)) + uassert.Equal(t, 4, l.Get(2)) +} + +func TestPrepend(t *testing.T) { + l := New(3) + + // Test adding within capacity + l.Prepend(1) + l.Prepend(2) + uassert.Equal(t, 2, l.Size()) + uassert.Equal(t, 2, l.Get(0)) + uassert.Equal(t, 1, l.Get(1)) + + // Test overflow behavior + l.Prepend(3) + l.Prepend(4) + uassert.Equal(t, 3, l.Size()) + uassert.Equal(t, 4, l.Get(0)) + uassert.Equal(t, 3, l.Get(1)) + uassert.Equal(t, 2, l.Get(2)) +} + +func TestGet(t *testing.T) { + l := New(3) + l.Append(1) + l.Append(2) + l.Append(3) + + // Test valid indices + uassert.Equal(t, 1, l.Get(0)) + uassert.Equal(t, 2, l.Get(1)) + uassert.Equal(t, 3, l.Get(2)) + + // Test invalid indices + uassert.True(t, l.Get(-1) == nil) + uassert.True(t, l.Get(3) == nil) +} + +func TestEntries(t *testing.T) { + l := New(3) + l.Append(1) + l.Append(2) + l.Append(3) + + entries := l.Entries() + uassert.Equal(t, 3, len(entries)) + uassert.Equal(t, 1, entries[0]) + uassert.Equal(t, 2, entries[1]) + uassert.Equal(t, 3, entries[2]) +} + +func TestLatest(t *testing.T) { + l := New(5) + + // Test empty list + uassert.True(t, l.Latest() == nil) + + // Test single entry + l.Append(1) + uassert.Equal(t, 1, l.Latest()) + + // Test multiple entries + l.Append(2) + l.Append(3) + uassert.Equal(t, 3, l.Latest()) + + // Test after overflow + l.Append(4) + l.Append(5) + l.Append(6) + uassert.Equal(t, 6, l.Latest()) +} + +func TestIterator(t *testing.T) { + l := New(3) + l.Append(1) + l.Append(2) + l.Append(3) + + iter := l.Iterator() + uassert.Equal(t, 1, iter()) + uassert.Equal(t, 2, iter()) + uassert.Equal(t, 3, iter()) + uassert.True(t, iter() == nil) +} + +func TestMixedOperations(t *testing.T) { + l := New(3) + + // Mix of append and prepend operations + l.Append(1) // [1] + l.Prepend(2) // [2,1] + l.Append(3) // [2,1,3] + l.Prepend(4) // [4,2,1] + + entries := l.Entries() + uassert.Equal(t, 3, len(entries)) + uassert.Equal(t, 4, entries[0]) + uassert.Equal(t, 2, entries[1]) + uassert.Equal(t, 1, entries[2]) +} + +func TestEmptyList(t *testing.T) { + l := New(3) + + // Test operations on empty list + uassert.Equal(t, 0, l.Size()) + uassert.True(t, l.Get(0) == nil) + uassert.Equal(t, 0, len(l.Entries())) + uassert.True(t, l.Latest() == nil) + + iter := l.Iterator() + uassert.True(t, iter() == nil) +} + +func TestEdgeCases(t *testing.T) { + // Test zero-size list + l := New(0) + uassert.Equal(t, 0, l.MaxSize()) + l.Append(1) // Should be no-op + uassert.Equal(t, 0, l.Size()) + + // Test single-element list + l = New(1) + l.Append(1) + l.Append(2) // Should replace 1 + uassert.Equal(t, 1, l.Size()) + uassert.Equal(t, 2, l.Latest()) + + // Test rapid append/prepend alternation + l = New(3) + l.Append(1) // [1] + l.Prepend(2) // [2,1] + l.Append(3) // [2,1,3] + l.Prepend(4) // [4,2,1] + l.Append(5) // [2,1,5] + uassert.Equal(t, 3, l.Size()) + entries := l.Entries() + uassert.Equal(t, 2, entries[0]) + uassert.Equal(t, 1, entries[1]) + uassert.Equal(t, 5, entries[2]) + + // Test nil values + l = New(2) + l.Append(nil) + l.Prepend(nil) + uassert.Equal(t, 2, l.Size()) + uassert.True(t, l.Get(0) == nil) + uassert.True(t, l.Get(1) == nil) + + // Test index bounds + l = New(3) + l.Append(1) + uassert.True(t, l.Get(-1) == nil) + uassert.True(t, l.Get(1) == nil) + + // Test iterator exhaustion + l = New(2) + l.Append(1) + l.Append(2) + iter := l.Iterator() + uassert.Equal(t, 1, iter()) + uassert.Equal(t, 2, iter()) + uassert.True(t, iter() == nil) + uassert.True(t, iter() == nil) + + // Test prepend on full list + l = New(2) + l.Append(1) + l.Append(2) // [1,2] + l.Prepend(3) // [3,1] + uassert.Equal(t, 2, l.Size()) + entries = l.Entries() + uassert.Equal(t, 3, entries[0]) + uassert.Equal(t, 1, entries[1]) +} + +func TestSetMaxSize(t *testing.T) { + l := New(5) + + // Fill the list + l.Append(1) + l.Append(2) + l.Append(3) + l.Append(4) + l.Append(5) + + // Test increasing maxSize + l.SetMaxSize(7) + uassert.Equal(t, 7, l.MaxSize()) + uassert.Equal(t, 5, l.Size()) + + // Test reducing maxSize + l.SetMaxSize(3) + uassert.Equal(t, 3, l.Size()) + entries := l.Entries() + uassert.Equal(t, 3, entries[0]) + uassert.Equal(t, 4, entries[1]) + uassert.Equal(t, 5, entries[2]) + + // Test setting to zero + l.SetMaxSize(0) + uassert.Equal(t, 0, l.Size()) + uassert.True(t, l.head == nil) + uassert.True(t, l.tail == nil) + + // Test negative maxSize + l.SetMaxSize(-1) + uassert.Equal(t, 0, l.MaxSize()) + + // Test setting back to positive + l.SetMaxSize(2) + l.Append(1) + l.Append(2) + l.Append(3) + uassert.Equal(t, 2, l.Size()) + entries = l.Entries() + uassert.Equal(t, 2, entries[0]) + uassert.Equal(t, 3, entries[1]) +} + +func TestDelete(t *testing.T) { + l := New(5) + + // Test delete on empty list + uassert.False(t, l.Delete(0)) + uassert.False(t, l.Delete(-1)) + + // Fill list + l.Append(1) + l.Append(2) + l.Append(3) + l.Append(4) + + // Test invalid indices + uassert.False(t, l.Delete(-1)) + uassert.False(t, l.Delete(4)) + + // Test deleting from middle + uassert.True(t, l.Delete(1)) + uassert.Equal(t, 3, l.Size()) + entries := l.Entries() + uassert.Equal(t, 1, entries[0]) + uassert.Equal(t, 3, entries[1]) + uassert.Equal(t, 4, entries[2]) + + // Test deleting from head + uassert.True(t, l.Delete(0)) + uassert.Equal(t, 2, l.Size()) + entries = l.Entries() + uassert.Equal(t, 3, entries[0]) + uassert.Equal(t, 4, entries[1]) + + // Test deleting from tail + uassert.True(t, l.Delete(1)) + uassert.Equal(t, 1, l.Size()) + uassert.Equal(t, 3, l.Latest()) + + // Test deleting last element + uassert.True(t, l.Delete(0)) + uassert.Equal(t, 0, l.Size()) + uassert.True(t, l.head == nil) + uassert.True(t, l.tail == nil) +} diff --git a/examples/gno.land/p/moul/fifo/gno.mod b/examples/gno.land/p/moul/fifo/gno.mod new file mode 100644 index 00000000000..dccbc39453b --- /dev/null +++ b/examples/gno.land/p/moul/fifo/gno.mod @@ -0,0 +1 @@ +module gno.land/p/moul/fifo