11package interpreter
22
33import (
4+ "fmt"
45 "math/big"
6+ "slices"
57)
68
79type Sender struct {
@@ -10,103 +12,44 @@ type Sender struct {
1012 Color string
1113}
1214
13- type queue [T any ] struct {
14- Head T
15- Tail * queue [T ]
16-
17- // Instead of keeping a single ref of the lastCell and updating the invariant on every push/pop operation,
18- // we keep a cache of the last cell on every cell.
19- // This makes code much easier and we don't risk breaking the invariant and producing wrong results and other subtle issues
20- //
21- // While, unlike keeping a single reference (like golang's queue `container/list` package does), this is not always O(1),
22- // the amortized time should still be O(1) (the number of steps of traversal while searching the last elem is not higher than the number of .Push() calls)
23- lastCell * queue [T ]
24- }
25-
26- func (s * queue [T ]) getLastCell () * queue [T ] {
27- // check if this is the last cell without reading cache first
28- if s .Tail == nil {
29- return s
30- }
31-
32- // if not, check if cache is present
33- if s .lastCell != nil {
34- // even if it is, it may be a stale value (as more values could have been pushed), so we check the value recursively
35- lastCell := s .lastCell .getLastCell ()
36- // we do path compression so that next time we get the path immediately
37- s .lastCell = lastCell
38- return lastCell
39- }
40-
41- // if no last value is cached, we traverse recursively to find it
42- s .lastCell = s .Tail .getLastCell ()
43- return s .lastCell
44- }
45-
46- func fromSlice [T any ](slice []T ) * queue [T ] {
47- var ret * queue [T ]
48- // TODO use https://pkg.go.dev/slices#Backward in golang 1.23
49- for i := len (slice ) - 1 ; i >= 0 ; i -- {
50- ret = & queue [T ]{
51- Head : slice [i ],
52- Tail : ret ,
53- }
54- }
55- return ret
56- }
57-
5815type fundsQueue struct {
59- senders * queue [ Sender ]
16+ senders [] Sender
6017}
6118
6219func newFundsQueue (senders []Sender ) fundsQueue {
63- return fundsQueue {
64- senders : fromSlice (senders ),
65- }
66- }
67-
68- func (s * fundsQueue ) compactTop () {
69- for s .senders != nil && s .senders .Tail != nil {
70-
71- first := s .senders .Head
72- second := s .senders .Tail .Head
73-
74- if second .Amount .Cmp (big .NewInt (0 )) == 0 {
75- s .senders = & queue [Sender ]{Head : first , Tail : s .senders .Tail .Tail }
76- continue
77- }
78-
79- if first .Name != second .Name || first .Color != second .Color {
80- return
81- }
82-
83- s .senders = & queue [Sender ]{
84- Head : Sender {
85- Name : first .Name ,
86- Color : first .Color ,
87- Amount : new (big.Int ).Add (first .Amount , second .Amount ),
88- },
89- Tail : s .senders .Tail .Tail ,
90- }
20+ queue := fundsQueue {
21+ senders : []Sender {},
9122 }
23+ queue .Push (senders ... )
24+ return queue
9225}
9326
27+ // Pull everything from this queue
9428func (s * fundsQueue ) PullAll () []Sender {
95- var senders []Sender
96- for s .senders != nil {
97- senders = append (senders , s .senders .Head )
98- s .senders = s .senders .Tail
99- }
29+ senders := s .senders
30+ s .senders = []Sender {} // TODO better heuristics for initial alloc
10031 return senders
10132}
10233
10334func (s * fundsQueue ) Push (senders ... Sender ) {
104- newTail := fromSlice (senders )
105- if s .senders == nil {
106- s .senders = newTail
35+ for _ , sender := range senders {
36+ s .PushOne (sender )
37+ }
38+ }
39+
40+ func (s * fundsQueue ) PushOne (sender Sender ) {
41+ if sender .Amount .Cmp (big .NewInt (0 )) == 0 {
42+ return
43+ }
44+ if len (s .senders ) == 0 {
45+ s .senders = []Sender {sender }
46+ return
47+ }
48+ last := s .senders [len (s .senders )- 1 ]
49+ if last .Name == sender .Name && last .Color == sender .Color {
50+ last .Amount .Add (last .Amount , sender .Amount )
10751 } else {
108- cell := s .senders .getLastCell ()
109- cell .Tail = newTail
52+ s .senders = append (s .senders , sender )
11053 }
11154}
11255
@@ -121,68 +64,71 @@ func (s *fundsQueue) PullUncolored(requiredAmount *big.Int) []Sender {
12164 return s .PullColored (requiredAmount , "" )
12265}
12366
124- func (s * fundsQueue ) Pull (requiredAmount * big.Int , color * string ) []Sender {
67+ // Pull at most maxAmount from this queue, with the given color
68+ func (s * fundsQueue ) Pull (maxAmount * big.Int , color * string ) []Sender {
12569 // clone so that we can manipulate this arg
126- requiredAmount = new (big.Int ).Set (requiredAmount )
70+ maxAmount = new (big.Int ).Set (maxAmount )
12771
12872 // TODO preallocate for perfs
129- var out []Sender
130-
131- for requiredAmount .Cmp (big .NewInt (0 )) != 0 && s .senders != nil {
132- s .compactTop ()
73+ out := newFundsQueue ([]Sender {})
74+ offset := 0
13375
134- available := s .senders . Head
135- s . senders = s .senders . Tail
76+ for maxAmount . Sign () > 0 && len ( s .senders ) > offset {
77+ frontSender : = s .senders [ offset ]
13678
137- if color != nil && available .Color != * color {
138- out1 := s .Pull (requiredAmount , color )
139- s .senders = & queue [Sender ]{
140- Head : available ,
141- Tail : s .senders ,
142- }
143- out = append (out , out1 ... )
144- break
79+ if color != nil && frontSender .Color != * color {
80+ offset += 1
81+ continue
14582 }
14683
147- switch available .Amount .Cmp (requiredAmount ) {
148- case - 1 : // not enough:
149- out = append (out , available )
150- requiredAmount .Sub (requiredAmount , available .Amount )
151-
152- case 1 : // more than enough
153- s .senders = & queue [Sender ]{
154- Head : Sender {
155- Name : available .Name ,
156- Color : available .Color ,
157- Amount : new (big.Int ).Sub (available .Amount , requiredAmount ),
158- },
159- Tail : s .senders ,
84+ switch frontSender .Amount .Cmp (maxAmount ) {
85+ case - 1 : // not enough
86+ maxAmount .Sub (maxAmount , frontSender .Amount )
87+ out .Push (frontSender )
88+ if offset == 0 {
89+ s .senders = s .senders [1 :]
90+ } else {
91+ s .senders = slices .Delete (s .senders , offset , offset + 1 )
16092 }
161- fallthrough
162-
163- case 0 : // exactly the same
164- out = append (out , Sender {
165- Name : available .Name ,
166- Color : available .Color ,
167- Amount : new (big.Int ).Set (requiredAmount ),
93+ case 1 : // more than enough
94+ out .Push (Sender {
95+ Name : frontSender .Name ,
96+ Amount : maxAmount ,
97+ Color : frontSender .Color ,
16898 })
169- return out
99+ s .senders [offset ].Amount .Sub (s .senders [offset ].Amount , maxAmount )
100+ return out .senders
101+ case 0 : // exactly enough
102+ out .Push (s .senders [offset ])
103+ if offset == 0 {
104+ s .senders = s .senders [1 :]
105+ } else {
106+ s .senders = slices .Delete (s .senders , offset , offset + 1 )
107+ }
108+ return out .senders
170109 }
171-
172110 }
173111
174- return out
112+ return out . senders
175113}
176114
177115// Clone the queue so that you can safely mutate one without mutating the other
178116func (s fundsQueue ) Clone () fundsQueue {
179- fq := newFundsQueue (nil )
117+ return newFundsQueue (s .senders )
118+ }
180119
181- senders := s .senders
182- for senders != nil {
183- fq .Push (senders .Head )
184- senders = senders .Tail
120+ func (s fundsQueue ) String () string {
121+ out := "<"
122+ for i , sender := range s .senders {
123+ if sender .Color == "" {
124+ out += fmt .Sprintf ("%v from %v" , sender .Amount , sender .Name )
125+ } else {
126+ out += fmt .Sprintf ("%v from %v\\ %v" , sender .Amount , sender .Name , sender .Color )
127+ }
128+ if i != len (s .senders )- 1 {
129+ out += ", "
130+ }
185131 }
186-
187- return fq
132+ out += ">"
133+ return out
188134}
0 commit comments