Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,20 @@ type Sender struct {
Color string
}

type stack[T any] struct {
type queue[T any] struct {
Head T
Tail *stack[T]
Tail *queue[T]

// Instead of keeping a single ref of the lastCell and updating the invariant on every push/pop operation,
// we keep a cache of the last cell on every cell.
// This makes code much easier and we don't risk breaking the invariant and producing wrong results and other subtle issues
//
// While, unlike keeping a single reference (like golang's queue `container/list` package does), this is not always O(1),
// 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)
lastCell *stack[T]
lastCell *queue[T]
}

func (s *stack[T]) getLastCell() *stack[T] {
func (s *queue[T]) getLastCell() *queue[T] {
// check if this is the last cell without reading cache first
if s.Tail == nil {
return s
Expand All @@ -43,44 +43,44 @@ func (s *stack[T]) getLastCell() *stack[T] {
return s.lastCell
}

func fromSlice[T any](slice []T) *stack[T] {
var ret *stack[T]
func fromSlice[T any](slice []T) *queue[T] {
var ret *queue[T]
// TODO use https://pkg.go.dev/slices#Backward in golang 1.23
for i := len(slice) - 1; i >= 0; i-- {
ret = &stack[T]{
ret = &queue[T]{
Head: slice[i],
Tail: ret,
}
}
return ret
}

type fundsStack struct {
senders *stack[Sender]
type fundsQueue struct {
senders *queue[Sender]
}

func newFundsStack(senders []Sender) fundsStack {
return fundsStack{
func newFundsQueue(senders []Sender) fundsQueue {
return fundsQueue{
senders: fromSlice(senders),
}
}

func (s *fundsStack) compactTop() {
func (s *fundsQueue) compactTop() {
for s.senders != nil && s.senders.Tail != nil {

first := s.senders.Head
second := s.senders.Tail.Head

if second.Amount.Cmp(big.NewInt(0)) == 0 {
s.senders = &stack[Sender]{Head: first, Tail: s.senders.Tail.Tail}
s.senders = &queue[Sender]{Head: first, Tail: s.senders.Tail.Tail}
continue
}

if first.Name != second.Name || first.Color != second.Color {
return
}

s.senders = &stack[Sender]{
s.senders = &queue[Sender]{
Head: Sender{
Name: first.Name,
Color: first.Color,
Expand All @@ -91,7 +91,7 @@ func (s *fundsStack) compactTop() {
}
}

func (s *fundsStack) PullAll() []Sender {
func (s *fundsQueue) PullAll() []Sender {
var senders []Sender
for s.senders != nil {
senders = append(senders, s.senders.Head)
Expand All @@ -100,7 +100,7 @@ func (s *fundsStack) PullAll() []Sender {
return senders
}

func (s *fundsStack) Push(senders ...Sender) {
func (s *fundsQueue) Push(senders ...Sender) {
newTail := fromSlice(senders)
if s.senders == nil {
s.senders = newTail
Expand All @@ -110,18 +110,18 @@ func (s *fundsStack) Push(senders ...Sender) {
}
}

func (s *fundsStack) PullAnything(requiredAmount *big.Int) []Sender {
func (s *fundsQueue) PullAnything(requiredAmount *big.Int) []Sender {
return s.Pull(requiredAmount, nil)
}

func (s *fundsStack) PullColored(requiredAmount *big.Int, color string) []Sender {
func (s *fundsQueue) PullColored(requiredAmount *big.Int, color string) []Sender {
return s.Pull(requiredAmount, &color)
}
func (s *fundsStack) PullUncolored(requiredAmount *big.Int) []Sender {
func (s *fundsQueue) PullUncolored(requiredAmount *big.Int) []Sender {
return s.PullColored(requiredAmount, "")
}

func (s *fundsStack) Pull(requiredAmount *big.Int, color *string) []Sender {
func (s *fundsQueue) Pull(requiredAmount *big.Int, color *string) []Sender {
// clone so that we can manipulate this arg
requiredAmount = new(big.Int).Set(requiredAmount)

Expand All @@ -136,7 +136,7 @@ func (s *fundsStack) Pull(requiredAmount *big.Int, color *string) []Sender {

if color != nil && available.Color != *color {
out1 := s.Pull(requiredAmount, color)
s.senders = &stack[Sender]{
s.senders = &queue[Sender]{
Head: available,
Tail: s.senders,
}
Expand All @@ -150,7 +150,7 @@ func (s *fundsStack) Pull(requiredAmount *big.Int, color *string) []Sender {
requiredAmount.Sub(requiredAmount, available.Amount)

case 1: // more than enough
s.senders = &stack[Sender]{
s.senders = &queue[Sender]{
Head: Sender{
Name: available.Name,
Color: available.Color,
Expand All @@ -174,15 +174,15 @@ func (s *fundsStack) Pull(requiredAmount *big.Int, color *string) []Sender {
return out
}

// Clone the stack so that you can safely mutate one without mutating the other
func (s fundsStack) Clone() fundsStack {
fs := newFundsStack(nil)
// Clone the queue so that you can safely mutate one without mutating the other
func (s fundsQueue) Clone() fundsQueue {
fq := newFundsQueue(nil)

senders := s.senders
for senders != nil {
fs.Push(senders.Head)
fq.Push(senders.Head)
senders = senders.Tail
}

return fs
return fq
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,146 +8,146 @@ import (
)

func TestEnoughBalance(t *testing.T) {
stack := newFundsStack([]Sender{
queue := newFundsQueue([]Sender{
{Name: "s1", Amount: big.NewInt(100)},
})

out := stack.PullAnything(big.NewInt(2))
out := queue.PullAnything(big.NewInt(2))
require.Equal(t, []Sender{
{Name: "s1", Amount: big.NewInt(2)},
}, out)

}

func TestPush(t *testing.T) {
stack := newFundsStack(nil)
stack.Push(Sender{Name: "acc", Amount: big.NewInt(100)})
queue := newFundsQueue(nil)
queue.Push(Sender{Name: "acc", Amount: big.NewInt(100)})

out := stack.PullUncolored(big.NewInt(20))
out := queue.PullUncolored(big.NewInt(20))
require.Equal(t, []Sender{
{Name: "acc", Amount: big.NewInt(20)},
}, out)

}

func TestSimple(t *testing.T) {
stack := newFundsStack([]Sender{
queue := newFundsQueue([]Sender{
{Name: "s1", Amount: big.NewInt(2)},
{Name: "s2", Amount: big.NewInt(10)},
})

out := stack.PullAnything(big.NewInt(5))
out := queue.PullAnything(big.NewInt(5))
require.Equal(t, []Sender{
{Name: "s1", Amount: big.NewInt(2)},
{Name: "s2", Amount: big.NewInt(3)},
}, out)

out = stack.PullAnything(big.NewInt(7))
out = queue.PullAnything(big.NewInt(7))
require.Equal(t, []Sender{
{Name: "s2", Amount: big.NewInt(7)},
}, out)
}

func TestPullZero(t *testing.T) {
stack := newFundsStack([]Sender{
queue := newFundsQueue([]Sender{
{Name: "s1", Amount: big.NewInt(2)},
{Name: "s2", Amount: big.NewInt(10)},
})

out := stack.PullAnything(big.NewInt(0))
out := queue.PullAnything(big.NewInt(0))
require.Equal(t, []Sender(nil), out)
}

func TestCompactFunds(t *testing.T) {
stack := newFundsStack([]Sender{
queue := newFundsQueue([]Sender{
{Name: "s1", Amount: big.NewInt(2)},
{Name: "s1", Amount: big.NewInt(10)},
})

out := stack.PullAnything(big.NewInt(5))
out := queue.PullAnything(big.NewInt(5))
require.Equal(t, []Sender{
{Name: "s1", Amount: big.NewInt(5)},
}, out)
}

func TestCompactFunds3Times(t *testing.T) {
stack := newFundsStack([]Sender{
queue := newFundsQueue([]Sender{
{Name: "s1", Amount: big.NewInt(2)},
{Name: "s1", Amount: big.NewInt(3)},
{Name: "s1", Amount: big.NewInt(1)},
})

out := stack.PullAnything(big.NewInt(6))
out := queue.PullAnything(big.NewInt(6))
require.Equal(t, []Sender{
{Name: "s1", Amount: big.NewInt(6)},
}, out)
}

func TestCompactFundsWithEmptySender(t *testing.T) {
stack := newFundsStack([]Sender{
queue := newFundsQueue([]Sender{
{Name: "s1", Amount: big.NewInt(2)},
{Name: "s2", Amount: big.NewInt(0)},
{Name: "s1", Amount: big.NewInt(10)},
})

out := stack.PullAnything(big.NewInt(5))
out := queue.PullAnything(big.NewInt(5))
require.Equal(t, []Sender{
{Name: "s1", Amount: big.NewInt(5)},
}, out)
}

func TestMissingFunds(t *testing.T) {
stack := newFundsStack([]Sender{
queue := newFundsQueue([]Sender{
{Name: "s1", Amount: big.NewInt(2)},
})

out := stack.PullAnything(big.NewInt(300))
out := queue.PullAnything(big.NewInt(300))
require.Equal(t, []Sender{
{Name: "s1", Amount: big.NewInt(2)},
}, out)
}

func TestNoZeroLeftovers(t *testing.T) {
stack := newFundsStack([]Sender{
queue := newFundsQueue([]Sender{
{Name: "s1", Amount: big.NewInt(10)},
{Name: "s2", Amount: big.NewInt(15)},
})

stack.PullAnything(big.NewInt(10))
queue.PullAnything(big.NewInt(10))

out := stack.PullAnything(big.NewInt(15))
out := queue.PullAnything(big.NewInt(15))
require.Equal(t, []Sender{
{Name: "s2", Amount: big.NewInt(15)},
}, out)
}

func TestReconcileColoredManyDestPerSender(t *testing.T) {
stack := newFundsStack([]Sender{
queue := newFundsQueue([]Sender{
{"src", big.NewInt(10), "X"},
})

out := stack.PullColored(big.NewInt(5), "X")
out := queue.PullColored(big.NewInt(5), "X")
require.Equal(t, []Sender{
{Name: "src", Amount: big.NewInt(5), Color: "X"},
}, out)

out = stack.PullColored(big.NewInt(5), "X")
out = queue.PullColored(big.NewInt(5), "X")
require.Equal(t, []Sender{
{Name: "src", Amount: big.NewInt(5), Color: "X"},
}, out)

}

func TestPullColored(t *testing.T) {
stack := newFundsStack([]Sender{
queue := newFundsQueue([]Sender{
{Name: "s1", Amount: big.NewInt(5)},
{Name: "s2", Amount: big.NewInt(1), Color: "red"},
{Name: "s3", Amount: big.NewInt(10)},
{Name: "s4", Amount: big.NewInt(2), Color: "red"},
{Name: "s5", Amount: big.NewInt(5)},
})

out := stack.PullColored(big.NewInt(2), "red")
out := queue.PullColored(big.NewInt(2), "red")
require.Equal(t, []Sender{
{Name: "s2", Amount: big.NewInt(1), Color: "red"},
{Name: "s4", Amount: big.NewInt(1), Color: "red"},
Expand All @@ -158,30 +158,30 @@ func TestPullColored(t *testing.T) {
{Name: "s3", Amount: big.NewInt(10)},
{Name: "s4", Amount: big.NewInt(1), Color: "red"},
{Name: "s5", Amount: big.NewInt(5)},
}, stack.PullAll())
}, queue.PullAll())
}

func TestPullColoredComplex(t *testing.T) {
stack := newFundsStack([]Sender{
queue := newFundsQueue([]Sender{
{"s1", big.NewInt(1), "c1"},
{"s2", big.NewInt(1), "c2"},
})

out := stack.PullColored(big.NewInt(1), "c2")
out := queue.PullColored(big.NewInt(1), "c2")
require.Equal(t, []Sender{
{Name: "s2", Amount: big.NewInt(1), Color: "c2"},
}, out)
}

func TestClone(t *testing.T) {

fs := newFundsStack([]Sender{
fq := newFundsQueue([]Sender{
{"s1", big.NewInt(10), ""},
})

cloned := fs.Clone()
cloned := fq.Clone()

fs.PullAll()
fq.PullAll()

require.Equal(t, []Sender{
{"s1", big.NewInt(10), ""},
Expand All @@ -192,19 +192,19 @@ func TestClone(t *testing.T) {
func TestCompactFundsAndPush(t *testing.T) {
noCol := ""

stack := newFundsStack([]Sender{
queue := newFundsQueue([]Sender{
{Name: "s1", Amount: big.NewInt(2)},
{Name: "s1", Amount: big.NewInt(10)},
})

stack.Pull(big.NewInt(1), &noCol)
queue.Pull(big.NewInt(1), &noCol)

stack.Push(Sender{
queue.Push(Sender{
Name: "pushed",
Amount: big.NewInt(42),
})

out := stack.PullAll()
out := queue.PullAll()
require.Equal(t, []Sender{
{Name: "s1", Amount: big.NewInt(11)},
{Name: "pushed", Amount: big.NewInt(42)},
Expand Down
Loading
Loading