/
sorted_list.go
193 lines (172 loc) · 4.85 KB
/
sorted_list.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
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
package twitch_chat_filter
import (
"strconv"
"time"
)
type MessageCount struct {
Message string
Count int
}
func (self *MessageCount) toString(threshold int) string {
if self.Count >= threshold {
return "(" + strconv.Itoa(self.Count) + ") " + self.Message
} else {
return ""
}
}
// updateAction Types
const (
Increment = iota
Decrement = iota
)
type updateAction struct {
Message string
Type int
}
type SortedMessages struct {
Data []MessageCount // no size limit
View []string // size limit applies
ViewSize int
ViewCountThreshold int
indices map[string]int
NotifyViewChange chan bool // an element is put in here every time the view changes
mailbox chan *updateAction // used to serialize updates one at a time. named after actor model
}
func NewSortedMessages(size, viewThreshold int) *SortedMessages {
sm := &SortedMessages{
Data: make([]MessageCount, 0),
View: make([]string, 0),
indices: make(map[string]int),
ViewSize: size,
mailbox: make(chan *updateAction, 200),
NotifyViewChange: make(chan bool, 200),
ViewCountThreshold: viewThreshold,
}
sm.initMessageWorker()
return sm
}
func (self *SortedMessages) initMessageWorker() {
go func() {
for {
mail := <-self.mailbox
if mail.Type == Increment {
self.increment(mail.Message)
} else if mail.Type == Decrement {
self.decrement(mail.Message)
}
}
}()
}
func (self *SortedMessages) Increment(message string, ttl time.Duration) {
self.mailbox <- &updateAction{message, Increment}
if ttl > 0 {
self.Decrement(message, ttl)
}
}
func (self *SortedMessages) Decrement(message string, delay time.Duration) {
action := updateAction{message, Decrement}
if delay > 0 {
go func() {
time.Sleep(delay)
self.mailbox <- &action
}()
} else {
self.mailbox <- &action
}
}
// Not threadsafe, use Increment.
func (self *SortedMessages) increment(message string) {
if i, ok := self.indices[message]; ok {
self.Data[i].Count += 1
if i < self.ViewSize {
self.View[i] = self.Data[i].toString(self.ViewCountThreshold)
}
// Sorted by descending order
for j := i - 1; j >= 0; i, j = i - 1, j - 1 {
if self.Data[j].Count < self.Data[i].Count {
self.swap(i, j)
} else {
break
}
}
} else {
// New message, put it at the back of the slice since it only has one count
newMessage := MessageCount{message, 1}
self.Data = append(self.Data, newMessage)
newIndex := len(self.Data) - 1
self.indices[message] = newIndex
// Update representation for UI if element is within size bounds
if newIndex < self.ViewSize {
self.View = append(self.View, self.Data[newIndex].toString(self.ViewCountThreshold))
}
}
self.NotifyViewChange <- true
}
// Not threadsafe, use Decrement.
func (self *SortedMessages) decrement(message string) {
if i, ok := self.indices[message]; ok {
// Decremented to 0 or less, get rid of index, remove from data and view
if count := self.Data[i].Count; count <= 1 {
delete(self.indices, message)
startIndexSecondHalf := Min(i + 1, len(self.Data))
rest := self.Data[startIndexSecondHalf:len(self.Data)]
self.Data = append(self.Data[:i], rest...)
// Update indices since we just shifted everything down one.
for j := i; j < len(self.Data); j++ {
e := self.Data[j]
self.indices[e.Message] = j
}
// Only try to remove item from view if it exists in the view
if i < len(self.View) {
a := self.View[:Min(i, len(self.View)-1)]
b := self.View[Min(i + 1, len(self.View)):len(self.View)]
self.View = append(a, b...)
}
// Since we just got rid of a reference in the View, we may need to pull in another one from the bottom of the Data slice
if len(self.View) < self.ViewSize && len(self.Data) >= self.ViewSize {
self.View = append(self.View, self.Data[len(self.View)].toString(self.ViewCountThreshold))
}
} else {
self.Data[i].Count -= 1
if i < self.ViewSize {
self.View[i] = self.Data[i].toString(self.ViewCountThreshold)
}
// [5,4,4,3,2,1]
// [5,3,4,3,2,1]
// [5,4,3,3,2,1]
// Just decremented a value, might need to move references down (-> that way).
for j := i + 1; j < len(self.Data); i, j = i + 1, j + 1 {
if self.Data[i].Count < self.Data[j].Count {
self.swap(i, j)
} else {
break
}
}
}
self.NotifyViewChange <- true
}
}
func (self *SortedMessages) swap(i int, j int) {
self.Data[i], self.Data[j] = self.Data[j], self.Data[i]
// Update view if within view size
if i < self.ViewSize {
self.View[i] = self.Data[i].toString(self.ViewCountThreshold)
}
if j < self.ViewSize {
self.View[j] = self.Data[j].toString(self.ViewCountThreshold)
}
self.indices[self.Data[j].Message] = j
self.indices[self.Data[i].Message] = i
}
func Min(x, y int) int {
if x < y {
return x
}
return y
}
func Max(x, y int) int {
if x > y {
return x
}
return y
}