-
Notifications
You must be signed in to change notification settings - Fork 0
/
queue.go
139 lines (129 loc) · 3.14 KB
/
queue.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
package plrucache
import (
"time"
)
// qItem contains value of queue item and timestamp.
type qItem[T comparable] struct {
// according to the docs time.Time conatins monotonic time, so
// it easy use time.Time to track timestampts instead of unixtime.
ts time.Time
prev int
next int
val T
}
type staticQ[T comparable] struct {
maxSize int
head int
tail int
slots []qItem[T]
freeSlots map[int]interface{}
}
// newQueue returns empty statis queue with defined size.
// Under the hood it used fixed size slice that implement double-linked list queue,
// that allows to pop least recentrly used for O(1), push new key for O(1) and hit the existing key
// for O(1) in case the caller has pointer to the assosiated item.
func newQueue[T comparable](size int) *staticQ[T] {
slots := make([]qItem[T], size)
freeSlots := make(map[int]interface{}, size)
for i := range slots {
freeSlots[i] = nil
}
return &staticQ[T]{
maxSize: size,
head: -1,
tail: -1,
slots: slots,
freeSlots: freeSlots,
}
}
// Push inserts new item with specified timestamp in the tail of the list.
// Important: for proper work of queue timestamps should monotonically increasing for every new item.
func (q *staticQ[T]) Push(val T, ts time.Time) int {
idx, ok := q.getFreeSlot()
if !ok {
// if no free slots left, pop least recently used
// and accure freed up one
q.Pop()
idx, _ = q.getFreeSlot()
}
q.slots[idx] = qItem[T]{
val: val,
ts: time.Now(),
prev: -1,
next: -1,
}
// if tail does not exist, head is also does not exist,
// so just put the first element into the list
if q.tail == -1 {
q.tail = idx
q.head = idx
return idx
}
oldTailIdx := q.tail
q.tail = idx
q.slots[idx].prev = oldTailIdx
q.slots[oldTailIdx].next = idx
return idx
}
// Pop removes and returns item from head of the list if there is one.
func (q *staticQ[T]) Pop() (qItem[T], bool) {
if q.head == -1 {
return qItem[T]{}, false
}
item := q.slots[q.head]
q.freeSlots[q.head] = nil // mark slots as free
if q.head == q.tail {
q.head = -1
q.tail = -1
} else {
q.slots[item.next].prev = -1
q.head = item.next
}
return item, true
}
// Delete key if it is exist.
func (q *staticQ[T]) Delete(idx int) bool {
if _, ok := q.freeSlots[idx]; ok {
return false
}
prev := q.slots[idx].prev
next := q.slots[idx].next
if prev >= 0 {
q.slots[prev].next = next
} else {
// it was the head item
q.head = next
}
if next >= 0 {
q.slots[next].prev = prev
} else {
// it was the tail item
q.tail = prev
}
q.freeSlots[idx] = nil
return true
}
func (q *staticQ[T]) Len() int {
return q.maxSize - len(q.freeSlots)
}
// Top returns item from head of the list if there is one.
func (q *staticQ[T]) Top() (qItem[T], bool) {
if q.head == -1 {
return qItem[T]{}, false
}
item := q.slots[q.head]
return item, true
}
// getFreeSlot returns random free slot in the slots slice, or (0, false) if no slots are available
func (q *staticQ[T]) getFreeSlot() (int, bool) {
if len(q.freeSlots) == 0 {
return 0, false
}
var idx int
// get any item from set
for idx = range q.freeSlots {
break
}
delete(q.freeSlots, idx)
return idx, true
}