Skip to content

Commit a049e38

Browse files
committed
mempool/txgraph: use dedicated stack/queue/pqueue collections for iterators
1 parent 42b42c8 commit a049e38

3 files changed

Lines changed: 830 additions & 67 deletions

File tree

mempool/txgraph/collections.go

Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
1+
package txgraph
2+
3+
import (
4+
"container/heap"
5+
"iter"
6+
)
7+
8+
// Stack implements a generic LIFO stack data structure with O(1) Push and Pop.
9+
// The zero value is ready to use.
10+
type Stack[T any] struct {
11+
items []T
12+
}
13+
14+
// NewStack creates a new empty stack with optional initial capacity.
15+
func NewStack[T any](capacity ...int) *Stack[T] {
16+
cap := 0
17+
if len(capacity) > 0 {
18+
cap = capacity[0]
19+
}
20+
return &Stack[T]{
21+
items: make([]T, 0, cap),
22+
}
23+
}
24+
25+
// Push adds an item to the top of the stack.
26+
func (s *Stack[T]) Push(item T) {
27+
s.items = append(s.items, item)
28+
}
29+
30+
// Pop removes and returns the item at the top of the stack.
31+
// Returns false if the stack is empty.
32+
func (s *Stack[T]) Pop() (T, bool) {
33+
if len(s.items) == 0 {
34+
var zero T
35+
return zero, false
36+
}
37+
idx := len(s.items) - 1
38+
item := s.items[idx]
39+
s.items = s.items[:idx]
40+
return item, true
41+
}
42+
43+
// Peek returns the item at the top of the stack without removing it.
44+
// Returns false if the stack is empty.
45+
func (s *Stack[T]) Peek() (T, bool) {
46+
if len(s.items) == 0 {
47+
var zero T
48+
return zero, false
49+
}
50+
return s.items[len(s.items)-1], true
51+
}
52+
53+
// Len returns the number of items in the stack.
54+
func (s *Stack[T]) Len() int {
55+
return len(s.items)
56+
}
57+
58+
// IsEmpty returns true if the stack contains no items.
59+
func (s *Stack[T]) IsEmpty() bool {
60+
return len(s.items) == 0
61+
}
62+
63+
// Clear removes all items from the stack.
64+
func (s *Stack[T]) Clear() {
65+
s.items = s.items[:0]
66+
}
67+
68+
// Iterate returns an iterator that yields items from top to bottom without
69+
// modifying the stack.
70+
func (s *Stack[T]) Iterate() iter.Seq[T] {
71+
return func(yield func(T) bool) {
72+
for i := len(s.items) - 1; i >= 0; i-- {
73+
if !yield(s.items[i]) {
74+
return
75+
}
76+
}
77+
}
78+
}
79+
80+
// Queue implements a generic FIFO queue with amortized O(1) Enqueue and
81+
// Dequeue operations. The zero value is ready to use.
82+
type Queue[T any] struct {
83+
items []T
84+
}
85+
86+
// NewQueue creates a new empty queue with optional initial capacity.
87+
func NewQueue[T any](capacity ...int) *Queue[T] {
88+
cap := 0
89+
if len(capacity) > 0 {
90+
cap = capacity[0]
91+
}
92+
return &Queue[T]{
93+
items: make([]T, 0, cap),
94+
}
95+
}
96+
97+
// Enqueue adds an item to the back of the queue.
98+
func (q *Queue[T]) Enqueue(item T) {
99+
q.items = append(q.items, item)
100+
}
101+
102+
// Dequeue removes and returns the item at the front of the queue.
103+
// Returns false if the queue is empty.
104+
func (q *Queue[T]) Dequeue() (T, bool) {
105+
if len(q.items) == 0 {
106+
var zero T
107+
return zero, false
108+
}
109+
item := q.items[0]
110+
q.items = q.items[1:]
111+
return item, true
112+
}
113+
114+
// Peek returns the item at the front of the queue without removing it.
115+
// Returns false if the queue is empty.
116+
func (q *Queue[T]) Peek() (T, bool) {
117+
if len(q.items) == 0 {
118+
var zero T
119+
return zero, false
120+
}
121+
return q.items[0], true
122+
}
123+
124+
// Len returns the number of items in the queue.
125+
func (q *Queue[T]) Len() int {
126+
return len(q.items)
127+
}
128+
129+
// IsEmpty returns true if the queue contains no items.
130+
func (q *Queue[T]) IsEmpty() bool {
131+
return len(q.items) == 0
132+
}
133+
134+
// Clear removes all items from the queue.
135+
func (q *Queue[T]) Clear() {
136+
q.items = q.items[:0]
137+
}
138+
139+
// Iterate returns an iterator that yields items from front to back without
140+
// modifying the queue.
141+
func (q *Queue[T]) Iterate() iter.Seq[T] {
142+
return func(yield func(T) bool) {
143+
for _, item := range q.items {
144+
if !yield(item) {
145+
return
146+
}
147+
}
148+
}
149+
}
150+
151+
// PriorityQueue implements a generic priority queue using container/heap
152+
// ordered by a comparison function. The zero value is NOT ready to use;
153+
// use NewPriorityQueue to create an instance.
154+
type PriorityQueue[T any] struct {
155+
impl *heapImpl[T]
156+
}
157+
158+
// NewPriorityQueue creates a new priority queue with the given comparison
159+
// function where less(a, b) returns true if a has higher priority than b.
160+
// For a max-heap use: func(a, b) { return a > b }.
161+
func NewPriorityQueue[T any](
162+
less func(a, b T) bool,
163+
capacity ...int,
164+
) *PriorityQueue[T] {
165+
166+
cap := 0
167+
if len(capacity) > 0 {
168+
cap = capacity[0]
169+
}
170+
return &PriorityQueue[T]{
171+
impl: &heapImpl[T]{
172+
items: make([]T, 0, cap),
173+
less: less,
174+
},
175+
}
176+
}
177+
178+
// Push adds an item to the priority queue.
179+
func (pq *PriorityQueue[T]) Push(item T) {
180+
heap.Push(pq.impl, item)
181+
}
182+
183+
// Pop removes and returns the highest priority item from the queue.
184+
// Returns false if the queue is empty.
185+
func (pq *PriorityQueue[T]) Pop() (T, bool) {
186+
if pq.impl.Len() == 0 {
187+
var zero T
188+
return zero, false
189+
}
190+
return heap.Pop(pq.impl).(T), true
191+
}
192+
193+
// Peek returns the highest priority item without removing it.
194+
// Returns false if the queue is empty.
195+
func (pq *PriorityQueue[T]) Peek() (T, bool) {
196+
if pq.impl.Len() == 0 {
197+
var zero T
198+
return zero, false
199+
}
200+
return pq.impl.items[0], true
201+
}
202+
203+
// Len returns the number of items in the priority queue.
204+
func (pq *PriorityQueue[T]) Len() int {
205+
return pq.impl.Len()
206+
}
207+
208+
// IsEmpty returns true if the priority queue contains no items.
209+
func (pq *PriorityQueue[T]) IsEmpty() bool {
210+
return pq.impl.Len() == 0
211+
}
212+
213+
// Clear removes all items from the priority queue.
214+
func (pq *PriorityQueue[T]) Clear() {
215+
pq.impl.items = pq.impl.items[:0]
216+
}
217+
218+
// Iterate returns an iterator that yields items in priority order by
219+
// creating a temporary copy to avoid modifying the original queue.
220+
func (pq *PriorityQueue[T]) Iterate() iter.Seq[T] {
221+
return func(yield func(T) bool) {
222+
tmpItems := make([]T, len(pq.impl.items))
223+
copy(tmpItems, pq.impl.items)
224+
tmp := &PriorityQueue[T]{
225+
impl: &heapImpl[T]{
226+
items: tmpItems,
227+
less: pq.impl.less,
228+
},
229+
}
230+
231+
for !tmp.IsEmpty() {
232+
item, _ := tmp.Pop()
233+
if !yield(item) {
234+
return
235+
}
236+
}
237+
}
238+
}
239+
240+
// heapImpl implements heap.Interface to integrate with container/heap.
241+
type heapImpl[T any] struct {
242+
items []T
243+
less func(a, b T) bool
244+
}
245+
246+
func (h *heapImpl[T]) Len() int {
247+
return len(h.items)
248+
}
249+
250+
func (h *heapImpl[T]) Less(i, j int) bool {
251+
return h.less(h.items[i], h.items[j])
252+
}
253+
254+
func (h *heapImpl[T]) Swap(i, j int) {
255+
h.items[i], h.items[j] = h.items[j], h.items[i]
256+
}
257+
258+
func (h *heapImpl[T]) Push(x any) {
259+
h.items = append(h.items, x.(T))
260+
}
261+
262+
func (h *heapImpl[T]) Pop() any {
263+
n := len(h.items) - 1
264+
item := h.items[n]
265+
h.items = h.items[:n]
266+
return item
267+
}
268+
269+
var _ heap.Interface = (*heapImpl[int])(nil)

0 commit comments

Comments
 (0)