Skip to content

Commit 53a472f

Browse files
authored
feat(examples): add p/moul/deque (#4895)
For a new banker demo, I wanted to create a ring buffer/FIFO to efficiently track and display the last N actions. I realized that we didn't have such a library yet, so here it is. --------- Signed-off-by: moul <[email protected]>
1 parent df30dec commit 53a472f

File tree

3 files changed

+711
-0
lines changed

3 files changed

+711
-0
lines changed
Lines changed: 326 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,326 @@
1+
// Package deque provides a doubly-linked deque with optional size limits,
2+
// optimized for minimal storage updates and O(1) operations in Gno environments.
3+
//
4+
// This implementation uses a doubly-linked list structure that provides:
5+
// - O(1) push back operations
6+
// - O(1) pop front operations
7+
// - O(1) access to first/last elements
8+
// - No array resizing or slice shifting
9+
// - Minimal allocations (one node per element)
10+
// - Minimal storage updates (typically 1-2 pointer updates per operation)
11+
// - Optional max-size eviction policy
12+
//
13+
// Optimized for Gno blockchain storage: each operation updates only specific nodes
14+
// rather than entire data structures, making it ideal for storage-efficient lists
15+
// that need simple read operations like first/last access, size checks, list
16+
// enumeration, and iteration with minimal storage update overhead.
17+
//
18+
// Example usage:
19+
//
20+
// // Unbounded deque
21+
// d := deque.New()
22+
// d.PushBack("a", "b", "c")
23+
//
24+
// // Bounded deque with automatic eviction
25+
// d = deque.NewBounded(3)
26+
// d.PushBack("a", "b", "c", "d") // "a" gets evicted
27+
//
28+
// first := d.PopFront() // Returns "b"
29+
// last := d.Last() // Returns "d"
30+
//
31+
// // Iterate forward
32+
// iter := d.All()
33+
// iter(func(val any) bool {
34+
// println(val)
35+
// return true // continue
36+
// })
37+
//
38+
// // Iterate backward
39+
// iter = d.Backward()
40+
// iter(func(val any) bool {
41+
// println(val)
42+
// return true // continue
43+
// })
44+
package deque
45+
46+
// Node represents a single element in the doubly-linked list
47+
type Node struct {
48+
value any
49+
prev *Node
50+
next *Node
51+
}
52+
53+
// Deque represents a doubly-linked deque with optional size limits
54+
type Deque struct {
55+
head *Node
56+
tail *Node
57+
size int
58+
max int // 0 = unlimited
59+
}
60+
61+
// New creates a new unbounded deque.
62+
func New() *Deque {
63+
return &Deque{
64+
max: 0, // unlimited
65+
}
66+
}
67+
68+
// NewBounded creates a new bounded deque with the specified maximum size.
69+
// When the deque exceeds maxSize, elements are removed from the front.
70+
func NewBounded(maxSize int) *Deque {
71+
if maxSize <= 0 {
72+
maxSize = 1
73+
}
74+
return &Deque{
75+
max: maxSize,
76+
}
77+
}
78+
79+
// PushBack adds one or more elements to the back of the deque.
80+
// If the deque exceeds maxSize, elements are removed from the front.
81+
func (d *Deque) PushBack(items ...any) {
82+
for _, item := range items {
83+
d.pushBackSingle(item)
84+
}
85+
}
86+
87+
// pushBackSingle adds a single element to the back
88+
func (d *Deque) pushBackSingle(item any) {
89+
node := &Node{value: item}
90+
91+
if d.tail == nil {
92+
// First element
93+
d.head = node
94+
d.tail = node
95+
} else {
96+
// Link to existing tail
97+
d.tail.next = node
98+
node.prev = d.tail
99+
d.tail = node
100+
}
101+
102+
d.size++
103+
104+
// Handle size limit by removing from front
105+
if d.max > 0 && d.size > d.max {
106+
d.popFrontNode()
107+
}
108+
}
109+
110+
// PopFront removes and returns the front element.
111+
// Returns nil if the deque is empty.
112+
func (d *Deque) PopFront() any {
113+
if d.head == nil {
114+
return nil
115+
}
116+
117+
value := d.head.value
118+
d.popFrontNode()
119+
return value
120+
}
121+
122+
// popFrontNode removes the front node
123+
func (d *Deque) popFrontNode() {
124+
if d.head == nil {
125+
return
126+
}
127+
128+
if d.head == d.tail {
129+
// Only one element
130+
d.head = nil
131+
d.tail = nil
132+
} else {
133+
// Move head forward
134+
d.head = d.head.next
135+
d.head.prev = nil
136+
}
137+
138+
d.size--
139+
}
140+
141+
// PopBack removes and returns the back element.
142+
// Returns nil if the deque is empty.
143+
func (d *Deque) PopBack() any {
144+
if d.tail == nil {
145+
return nil
146+
}
147+
148+
value := d.tail.value
149+
d.popBackNode()
150+
return value
151+
}
152+
153+
// popBackNode removes the back node
154+
func (d *Deque) popBackNode() {
155+
if d.tail == nil {
156+
return
157+
}
158+
159+
if d.head == d.tail {
160+
// Only one element
161+
d.head = nil
162+
d.tail = nil
163+
} else {
164+
// Move tail backward
165+
d.tail = d.tail.prev
166+
d.tail.next = nil
167+
}
168+
169+
d.size--
170+
}
171+
172+
// PushFront adds one or more elements to the front of the deque.
173+
// If bounded and size limit is exceeded, elements are removed from the back.
174+
func (d *Deque) PushFront(items ...any) {
175+
for _, item := range items {
176+
d.pushFrontSingle(item)
177+
}
178+
}
179+
180+
// pushFrontSingle adds a single element to the front
181+
func (d *Deque) pushFrontSingle(item any) {
182+
node := &Node{value: item}
183+
184+
if d.head == nil {
185+
// First element
186+
d.head = node
187+
d.tail = node
188+
} else {
189+
// Link to existing head
190+
d.head.prev = node
191+
node.next = d.head
192+
d.head = node
193+
}
194+
195+
d.size++
196+
197+
// Handle size limit by removing from back
198+
if d.max > 0 && d.size > d.max {
199+
d.popBackNode()
200+
}
201+
}
202+
203+
// Size returns the current number of elements
204+
func (d *Deque) Size() int {
205+
return d.size
206+
}
207+
208+
// MaxSize returns the maximum size limit (0 = unlimited)
209+
func (d *Deque) MaxSize() int {
210+
return d.max
211+
}
212+
213+
// IsEmpty returns true if the deque is empty
214+
func (d *Deque) IsEmpty() bool {
215+
return d.size == 0
216+
}
217+
218+
// IsBounded returns true if the deque has a size limit
219+
func (d *Deque) IsBounded() bool {
220+
return d.max > 0
221+
}
222+
223+
// First returns the front element without removing it.
224+
// Returns nil if the deque is empty.
225+
func (d *Deque) First() any {
226+
if d.head == nil {
227+
return nil
228+
}
229+
return d.head.value
230+
}
231+
232+
// Last returns the back element without removing it.
233+
// Returns nil if the deque is empty.
234+
func (d *Deque) Last() any {
235+
if d.tail == nil {
236+
return nil
237+
}
238+
return d.tail.value
239+
}
240+
241+
// Get returns the element at the specified index (0 = front).
242+
// Returns nil if index is out of bounds.
243+
func (d *Deque) Get(index int) any {
244+
if index < 0 || index >= d.size {
245+
return nil
246+
}
247+
248+
node := d.head
249+
for i := 0; i < index; i++ {
250+
node = node.next
251+
}
252+
return node.value
253+
}
254+
255+
// List returns all elements as a slice, ordered from front to back
256+
func (d *Deque) List() []any {
257+
if d.size == 0 {
258+
return nil
259+
}
260+
261+
result := make([]any, d.size)
262+
node := d.head
263+
for i := 0; i < d.size; i++ {
264+
result[i] = node.value
265+
node = node.next
266+
}
267+
return result
268+
}
269+
270+
// Enumerate calls fn for each element with its index (0-based from front)
271+
func (d *Deque) Enumerate(fn func(index int, value any) bool) {
272+
node := d.head
273+
for i := 0; i < d.size; i++ {
274+
if fn(i, node.value) {
275+
break
276+
}
277+
node = node.next
278+
}
279+
}
280+
281+
// Clear removes all elements from the deque
282+
func (d *Deque) Clear() {
283+
d.head = nil
284+
d.tail = nil
285+
d.size = 0
286+
}
287+
288+
// SetMaxSize changes the maximum size limit.
289+
// If the new limit is smaller than current size, elements are removed from front.
290+
func (d *Deque) SetMaxSize(maxSize int) {
291+
d.max = maxSize
292+
293+
if maxSize > 0 {
294+
for d.size > maxSize {
295+
d.popFrontNode()
296+
}
297+
}
298+
}
299+
300+
// All returns an iterator function for forward traversal (Go 1.23+ compatible)
301+
// Note: range-over-func syntax is not yet supported in Gno, use iter() directly
302+
func (d *Deque) All() func(yield func(any) bool) {
303+
return func(yield func(any) bool) {
304+
current := d.head
305+
for current != nil {
306+
if !yield(current.value) {
307+
return
308+
}
309+
current = current.next
310+
}
311+
}
312+
}
313+
314+
// Backward returns an iterator function for backward traversal (Go 1.23+ compatible)
315+
// Note: range-over-func syntax is not yet supported in Gno, use iter() directly
316+
func (d *Deque) Backward() func(yield func(any) bool) {
317+
return func(yield func(any) bool) {
318+
current := d.tail
319+
for current != nil {
320+
if !yield(current.value) {
321+
return
322+
}
323+
current = current.prev
324+
}
325+
}
326+
}

0 commit comments

Comments
 (0)