maybe i should just write down the crdt stuff
parent
e435841c72
commit
5d97f3a8f0
@ -0,0 +1,60 @@
|
||||
package crdt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"constraints"
|
||||
)
|
||||
|
||||
func max[N constraints.Ordered](a, b N) N {
|
||||
if a >= b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func NewGCounter[K comparable]() GCounter[K] {
|
||||
return GCounter[K]{slots: make(map[K]int)}
|
||||
}
|
||||
|
||||
type GCounter[K comparable] struct {
|
||||
slots map[K]int `json:"slots"`
|
||||
}
|
||||
|
||||
func (g GCounter[K]) Incr(slot K) error {
|
||||
var zero K
|
||||
if slot == zero {
|
||||
return fmt.Errorf("gcounter refuses incr on the zero-value of its key")
|
||||
}
|
||||
|
||||
g.slots[slot]++
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g GCounter[K]) Add(slot K, delta int) error {
|
||||
var zero K
|
||||
if slot == zero {
|
||||
return fmt.Errorf("gcounter refuses add on the zero-value of its key")
|
||||
}
|
||||
|
||||
if delta < 0 {
|
||||
return fmt.Errorf("gcounters cannot go down, use a pncounter instead")
|
||||
}
|
||||
g.slots[slot] += delta
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *GCounter[K]) Merge(dest *GCounter[K]) {
|
||||
for slot, count := range g.slots {
|
||||
v := max(count, dest.slots[slot])
|
||||
dest.slots[slot] = v
|
||||
g.slots[slot] = v
|
||||
}
|
||||
}
|
||||
|
||||
func (g *GCounter[K]) Total() int {
|
||||
var n int
|
||||
for _, count := range g.slots {
|
||||
n += count
|
||||
}
|
||||
return n
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
package crdt
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGCounter(t *testing.T) {
|
||||
t.Run("incr", func(t *testing.T) {
|
||||
g := NewGCounter[string]()
|
||||
if n := g.Total(); n != 0 {
|
||||
t.Fatalf("new gcounter has count of %d, should be 0", n)
|
||||
}
|
||||
|
||||
if err := g.Incr("jordan"); err != nil {
|
||||
t.Fatalf("gcounter failed incr: %v", err)
|
||||
}
|
||||
|
||||
if n := g.Total(); n != 1 {
|
||||
t.Fatalf("new gcounter has count of %d, should be 1", n)
|
||||
}
|
||||
|
||||
if err := g.Incr(""); err == nil {
|
||||
t.Fatalf("incrementing the zero value succeeded, should have failed")
|
||||
}
|
||||
|
||||
if err := g.Incr("jordan"); err != nil {
|
||||
t.Fatalf("gcounter failed incr: %v", err)
|
||||
}
|
||||
|
||||
if n := g.Total(); n != 2 {
|
||||
t.Fatalf("new gcounter has count of %d, should be 2", n)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("add", func(t *testing.T) {
|
||||
g := NewGCounter[string]()
|
||||
if n := g.Total(); n != 0 {
|
||||
t.Fatalf("new gcounter has count of %d, should be 0", n)
|
||||
}
|
||||
|
||||
if err := g.Add("jordan", 4); err != nil {
|
||||
t.Fatalf("gcounter failed incr: %v", err)
|
||||
}
|
||||
|
||||
if n := g.Total(); n != 4 {
|
||||
t.Fatalf("new gcounter has count of %d, should be 1", n)
|
||||
}
|
||||
|
||||
if err := g.Add("", 10); err == nil {
|
||||
t.Fatalf("adding to zero key succeeded, should have failed")
|
||||
}
|
||||
|
||||
if err := g.Add("jordan", -3); err == nil {
|
||||
t.Fatalf("adding negatively to the gcounter succeeded, should have failed")
|
||||
}
|
||||
|
||||
if err := g.Add("jordan", 3); err != nil {
|
||||
t.Fatalf("gcounter failed add: %v", err)
|
||||
}
|
||||
|
||||
if n := g.Total(); n != 7 {
|
||||
t.Fatalf("new gcounter has count of %d, should be 7", n)
|
||||
}
|
||||
})
|
||||
}
|
Loading…
Reference in New Issue