Skip to content Search Apps Documentation Source Content File Folder Download Copy Actions State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

ulist.gno

10.68 Kb · 439 lines
  1// Package ulist provides an append-only list implementation using a binary tree structure,
  2// optimized for scenarios requiring sequential inserts with auto-incrementing indices.
  3//
  4// The implementation uses a binary tree where new elements are added by following a path
  5// determined by the binary representation of the index. This provides automatic balancing
  6// for append operations without requiring any balancing logic.
  7//
  8// Unlike the AVL tree-based list implementation (p/demo/avl/list), ulist is specifically
  9// designed for append-only operations and does not require rebalancing. This makes it more
 10// efficient for sequential inserts but less flexible for general-purpose list operations.
 11//
 12// Key differences from AVL list:
 13// * Append-only design (no arbitrary inserts)
 14// * No tree rebalancing needed
 15// * Simpler implementation
 16// * More memory efficient for sequential operations
 17// * Less flexible than AVL (no arbitrary inserts/reordering)
 18//
 19// Key characteristics:
 20// * O(log n) append and access operations
 21// * Perfect balance for power-of-2 sizes
 22// * No balancing needed
 23// * Memory efficient
 24// * Natural support for range queries
 25// * Support for soft deletion of elements
 26// * Forward and reverse iteration capabilities
 27// * Offset-based iteration with count control
 28package ulist
 29
 30// TODO: Make avl/pager compatible in some way. Explain the limitations (not always 10 items because of nil ones).
 31// TODO: Use this ulist in moul/collection for the primary index.
 32// TODO: Consider adding a "compact" method that removes nil nodes.
 33// TODO: Benchmarks.
 34
 35import (
 36	"errors"
 37)
 38
 39// List represents an append-only binary tree list
 40type List struct {
 41	root       *treeNode
 42	totalSize  int
 43	activeSize int
 44}
 45
 46// Entry represents a key-value pair in the list, where Index is the position
 47// and Value is the stored data
 48type Entry struct {
 49	Index int
 50	Value any
 51}
 52
 53// treeNode represents a node in the binary tree
 54type treeNode struct {
 55	data  any
 56	left  *treeNode
 57	right *treeNode
 58}
 59
 60// Error variables
 61var (
 62	ErrOutOfBounds = errors.New("index out of bounds")
 63	ErrDeleted     = errors.New("element already deleted")
 64)
 65
 66// New creates a new empty List instance
 67func New() *List {
 68	return &List{}
 69}
 70
 71// Append adds one or more values to the end of the list.
 72// Values are added sequentially, and the list grows automatically.
 73func (l *List) Append(values ...any) {
 74	for _, value := range values {
 75		index := l.totalSize
 76		node := l.findNode(index, true)
 77		node.data = value
 78		l.totalSize++
 79		l.activeSize++
 80	}
 81}
 82
 83// Get retrieves the value at the specified index.
 84// Returns nil if the index is out of bounds or if the element was deleted.
 85func (l *List) Get(index int) any {
 86	node := l.findNode(index, false)
 87	if node == nil {
 88		return nil
 89	}
 90	return node.data
 91}
 92
 93// Delete marks the elements at the specified indices as deleted.
 94// Returns ErrOutOfBounds if any index is invalid or ErrDeleted if
 95// the element was already deleted.
 96func (l *List) Delete(indices ...int) error {
 97	if len(indices) == 0 {
 98		return nil
 99	}
100	if l == nil || l.totalSize == 0 {
101		return ErrOutOfBounds
102	}
103
104	for _, index := range indices {
105		if index < 0 || index >= l.totalSize {
106			return ErrOutOfBounds
107		}
108
109		node := l.findNode(index, false)
110		if node == nil || node.data == nil {
111			return ErrDeleted
112		}
113		node.data = nil
114		l.activeSize--
115	}
116
117	return nil
118}
119
120// Set updates or restores a value at the specified index if within bounds
121// Returns ErrOutOfBounds if the index is invalid
122func (l *List) Set(index int, value any) error {
123	if l == nil || index < 0 || index >= l.totalSize {
124		return ErrOutOfBounds
125	}
126
127	node := l.findNode(index, false)
128	if node == nil {
129		return ErrOutOfBounds
130	}
131
132	// If this is restoring a deleted element
133	if value != nil && node.data == nil {
134		l.activeSize++
135	}
136
137	// If this is deleting an element
138	if value == nil && node.data != nil {
139		l.activeSize--
140	}
141
142	node.data = value
143	return nil
144}
145
146// Size returns the number of active (non-deleted) elements in the list
147func (l *List) Size() int {
148	if l == nil {
149		return 0
150	}
151	return l.activeSize
152}
153
154// TotalSize returns the total number of elements ever added to the list,
155// including deleted elements
156func (l *List) TotalSize() int {
157	if l == nil {
158		return 0
159	}
160	return l.totalSize
161}
162
163// IterCbFn is a callback function type used in iteration methods.
164// Return true to stop iteration, false to continue.
165type IterCbFn func(index int, value any) bool
166
167// Iterator performs iteration between start and end indices, calling cb for each entry.
168// If start > end, iteration is performed in reverse order.
169// Returns true if iteration was stopped early by the callback returning true.
170// Skips deleted elements.
171func (l *List) Iterator(start, end int, cb IterCbFn) bool {
172	// For empty list or invalid range
173	if l == nil || l.totalSize == 0 {
174		return false
175	}
176	if start < 0 && end < 0 {
177		return false
178	}
179	if start >= l.totalSize && end >= l.totalSize {
180		return false
181	}
182
183	// Normalize indices
184	if start < 0 {
185		start = 0
186	}
187	if end < 0 {
188		end = 0
189	}
190	if end >= l.totalSize {
191		end = l.totalSize - 1
192	}
193	if start >= l.totalSize {
194		start = l.totalSize - 1
195	}
196
197	// Handle reverse iteration
198	if start > end {
199		for i := start; i >= end; i-- {
200			val := l.Get(i)
201			if val != nil {
202				if cb(i, val) {
203					return true
204				}
205			}
206		}
207		return false
208	}
209
210	// Handle forward iteration
211	for i := start; i <= end; i++ {
212		val := l.Get(i)
213		if val != nil {
214			if cb(i, val) {
215				return true
216			}
217		}
218	}
219	return false
220}
221
222// IteratorByOffset performs iteration starting from offset for count elements.
223// If count is positive, iterates forward; if negative, iterates backward.
224// The iteration stops after abs(count) elements or when reaching list bounds.
225// Skips deleted elements.
226func (l *List) IteratorByOffset(offset int, count int, cb IterCbFn) bool {
227	if count == 0 || l == nil || l.totalSize == 0 {
228		return false
229	}
230
231	// Normalize offset
232	if offset < 0 {
233		offset = 0
234	}
235	if offset >= l.totalSize {
236		offset = l.totalSize - 1
237	}
238
239	// Determine end based on count direction
240	var end int
241	if count > 0 {
242		end = l.totalSize - 1
243	} else {
244		end = 0
245	}
246
247	wrapperReturned := false
248
249	// Wrap the callback to limit iterations
250	remaining := abs(count)
251	wrapper := func(index int, value any) bool {
252		if remaining <= 0 {
253			wrapperReturned = true
254			return true
255		}
256		remaining--
257		return cb(index, value)
258	}
259	ret := l.Iterator(offset, end, wrapper)
260	if wrapperReturned {
261		return false
262	}
263	return ret
264}
265
266// abs returns the absolute value of x
267func abs(x int) int {
268	if x < 0 {
269		return -x
270	}
271	return x
272}
273
274// findNode locates or creates a node at the given index in the binary tree.
275// The tree is structured such that the path to a node is determined by the binary
276// representation of the index. For example, a tree with 15 elements would look like:
277//
278//	          0
279//	       /      \
280//	     1         2
281//	   /   \     /   \
282//	  3    4    5     6
283//	 / \  / \  / \   / \
284//	7  8 9 10 11 12 13 14
285//
286// To find index 13 (binary 1101):
287// 1. Start at root (0)
288// 2. Calculate bits needed (4 bits for index 13)
289// 3. Skip the highest bit position and start from bits-2
290// 4. Read bits from left to right:
291//   - 1 -> go right to 2
292//   - 1 -> go right to 6
293//   - 0 -> go left to 13
294//
295// Special cases:
296// - Index 0 always returns the root node
297// - For create=true, missing nodes are created along the path
298// - For create=false, returns nil if any node is missing
299func (l *List) findNode(index int, create bool) *treeNode {
300	// For read operations, check bounds strictly
301	if !create && (l == nil || index < 0 || index >= l.totalSize) {
302		return nil
303	}
304
305	// For create operations, allow index == totalSize for append
306	if create && (l == nil || index < 0 || index > l.totalSize) {
307		return nil
308	}
309
310	// Initialize root if needed
311	if l.root == nil {
312		if !create {
313			return nil
314		}
315		l.root = &treeNode{}
316		return l.root
317	}
318
319	node := l.root
320
321	// Special case for root node
322	if index == 0 {
323		return node
324	}
325
326	// Calculate the number of bits needed (inline highestBit logic)
327	bits := 0
328	n := index + 1
329	for n > 0 {
330		n >>= 1
331		bits++
332	}
333
334	// Start from the second highest bit
335	for level := bits - 2; level >= 0; level-- {
336		bit := (index & (1 << uint(level))) != 0
337
338		if bit {
339			if node.right == nil {
340				if !create {
341					return nil
342				}
343				node.right = &treeNode{}
344			}
345			node = node.right
346		} else {
347			if node.left == nil {
348				if !create {
349					return nil
350				}
351				node.left = &treeNode{}
352			}
353			node = node.left
354		}
355	}
356
357	return node
358}
359
360// MustDelete deletes elements at the specified indices.
361// Panics if any index is invalid or if any element was already deleted.
362func (l *List) MustDelete(indices ...int) {
363	if err := l.Delete(indices...); err != nil {
364		panic(err)
365	}
366}
367
368// MustGet retrieves the value at the specified index.
369// Panics if the index is out of bounds or if the element was deleted.
370func (l *List) MustGet(index int) any {
371	if l == nil || index < 0 || index >= l.totalSize {
372		panic(ErrOutOfBounds)
373	}
374	value := l.Get(index)
375	if value == nil {
376		panic(ErrDeleted)
377	}
378	return value
379}
380
381// MustSet updates or restores a value at the specified index.
382// Panics if the index is out of bounds.
383func (l *List) MustSet(index int, value any) {
384	if err := l.Set(index, value); err != nil {
385		panic(err)
386	}
387}
388
389// GetRange returns a slice of Entry containing elements between start and end indices.
390// If start > end, elements are returned in reverse order.
391// Deleted elements are skipped.
392func (l *List) GetRange(start, end int) []Entry {
393	var entries []Entry
394	l.Iterator(start, end, func(index int, value any) bool {
395		entries = append(entries, Entry{Index: index, Value: value})
396		return false
397	})
398	return entries
399}
400
401// GetByOffset returns a slice of Entry starting from offset for count elements.
402// If count is positive, returns elements forward; if negative, returns elements backward.
403// The operation stops after abs(count) elements or when reaching list bounds.
404// Deleted elements are skipped.
405func (l *List) GetByOffset(offset int, count int) []Entry {
406	var entries []Entry
407	l.IteratorByOffset(offset, count, func(index int, value any) bool {
408		entries = append(entries, Entry{Index: index, Value: value})
409		return false
410	})
411	return entries
412}
413
414// IList defines the interface for an ulist.List compatible structure.
415type IList interface {
416	// Basic operations
417	Append(values ...any)
418	Get(index int) any
419	Delete(indices ...int) error
420	Size() int
421	TotalSize() int
422	Set(index int, value any) error
423
424	// Must variants that panic instead of returning errors
425	MustDelete(indices ...int)
426	MustGet(index int) any
427	MustSet(index int, value any)
428
429	// Range operations
430	GetRange(start, end int) []Entry
431	GetByOffset(offset int, count int) []Entry
432
433	// Iterator operations
434	Iterator(start, end int, cb IterCbFn) bool
435	IteratorByOffset(offset int, count int, cb IterCbFn) bool
436}
437
438// Verify that List implements IList
439var _ IList = (*List)(nil)