mirror of
https://github.com/luxfi/math.git
synced 2026-09-24 00:25:16 +00:00
Initial commit: Lux math library
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
# Lux Math Library
|
||||
|
||||
A comprehensive mathematical utilities library for the Lux ecosystem.
|
||||
|
||||
## Features
|
||||
|
||||
- **Math utilities**: Safe math operations, averagers, and meters
|
||||
- **Set operations**: Efficient set implementations including bit sets
|
||||
- **Numerical computing**: Integration with gonum for advanced mathematical operations
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
go get github.com/luxfi/math
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```go
|
||||
import (
|
||||
"github.com/luxfi/math/math"
|
||||
"github.com/luxfi/math/set"
|
||||
)
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
See the LICENSE file for licensing terms.
|
||||
@@ -0,0 +1,23 @@
|
||||
module github.com/luxfi/math
|
||||
|
||||
go 1.24.6
|
||||
|
||||
require (
|
||||
github.com/luxfi/geth v1.16.34
|
||||
github.com/luxfi/ids v1.0.2
|
||||
github.com/luxfi/node v1.16.15
|
||||
github.com/stretchr/testify v1.10.0
|
||||
golang.org/x/exp v0.0.0-20250813145105-42675adae3e6
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/gorilla/rpc v1.2.1 // indirect
|
||||
github.com/luxfi/crypto v1.2.9 // indirect
|
||||
github.com/mr-tron/base58 v1.2.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
go.uber.org/mock v0.5.2 // indirect
|
||||
golang.org/x/crypto v0.40.0 // indirect
|
||||
gonum.org/v1/gonum v0.16.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
@@ -0,0 +1,41 @@
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/gorilla/rpc v1.2.1 h1:yC+LMV5esttgpVvNORL/xX4jvTTEUE30UZhZ5JF7K9k=
|
||||
github.com/gorilla/rpc v1.2.1/go.mod h1:uNpOihAlF5xRFLuTYhfR0yfCTm0WTQSQttkMSptRfGk=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/luxfi/crypto v1.2.9 h1:Ecqznogm85AK9IniUL5VRHXehGnlMLO6wE9Tsqhudfo=
|
||||
github.com/luxfi/crypto v1.2.9/go.mod h1:XuwNS3m4bJEgxt4myw0woWszz3/aFy5EgZInR7JLu9Q=
|
||||
github.com/luxfi/geth v1.16.34 h1:61IMMZMWfi4bM4SASVBvp3x+wYmpcTpIIHDiKVVPDiQ=
|
||||
github.com/luxfi/geth v1.16.34/go.mod h1:kjF/Vwzd9Iz6+9PpHyF7ZtfVJYsJdBYGhnoXLDxx7b8=
|
||||
github.com/luxfi/ids v1.0.2 h1:OnbCWBgdmuBPZat1r1vRikk1FFrdHMfSknpfKmX0KBg=
|
||||
github.com/luxfi/ids v1.0.2/go.mod h1:cMbto3Q8N3IaBxdz4Lzaq9wYl33coGXUdlr2+YwXeUw=
|
||||
github.com/luxfi/node v1.16.15 h1:ZRsteUu6ObB7Or3E6ZliNY2w/cL4tByGaSm7J37sVU8=
|
||||
github.com/luxfi/node v1.16.15/go.mod h1:05FhncqqiDSp5XfSPjoPvTBBUVokCdKpHL72aRCxs4Y=
|
||||
github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
|
||||
github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||
github.com/sanity-io/litter v1.5.5 h1:iE+sBxPBzoK6uaEP5Lt3fHNgpKcHXc/A2HGETy0uJQo=
|
||||
github.com/sanity-io/litter v1.5.5/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/thepudds/fzgen v0.4.3 h1:srUP/34BulQaEwPP/uHZkdjUcUjIzL7Jkf4CBVryiP8=
|
||||
github.com/thepudds/fzgen v0.4.3/go.mod h1:BhhwtRhzgvLWAjjcHDJ9pEiLD2Z9hrVIFjBCHJ//zJ4=
|
||||
go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=
|
||||
go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o=
|
||||
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
|
||||
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
|
||||
golang.org/x/exp v0.0.0-20250813145105-42675adae3e6 h1:SbTAbRFnd5kjQXbczszQ0hdk3ctwYf3qBNH9jIsGclE=
|
||||
golang.org/x/exp v0.0.0-20250813145105-42675adae3e6/go.mod h1:4QTo5u+SEIbbKW1RacMZq1YEfOBqeXa19JeshGi+zc4=
|
||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
@@ -0,0 +1,16 @@
|
||||
// Copyright (C) 2019-2024, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package math
|
||||
|
||||
import "time"
|
||||
|
||||
// Averager tracks a continuous time exponential moving average of the provided
|
||||
// values.
|
||||
type Averager interface {
|
||||
// Observe the value at the given time
|
||||
Observe(value float64, currentTime time.Time)
|
||||
|
||||
// Read returns the average of the provided values.
|
||||
Read() float64
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
// Copyright (C) 2019-2024, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package math
|
||||
|
||||
import (
|
||||
"github.com/luxfi/ids"
|
||||
"github.com/luxfi/node/utils/heap"
|
||||
)
|
||||
|
||||
var _ AveragerHeap = (*averagerHeap)(nil)
|
||||
|
||||
// TODO replace this interface with utils/heap
|
||||
// AveragerHeap maintains a heap of the averagers.
|
||||
type AveragerHeap interface {
|
||||
// Add the average to the heap. If [nodeID] is already in the heap, the
|
||||
// average will be replaced and the old average will be returned. If there
|
||||
// was not an old average, false will be returned.
|
||||
Add(nodeID ids.NodeID, averager Averager) (Averager, bool)
|
||||
// Remove attempts to remove the average that was added with the provided
|
||||
// [nodeID], if none is contained in the heap, [false] will be returned.
|
||||
Remove(nodeID ids.NodeID) (Averager, bool)
|
||||
// Pop attempts to remove the node with either the largest or smallest
|
||||
// average, depending on if this is a max heap or a min heap, respectively.
|
||||
Pop() (ids.NodeID, Averager, bool)
|
||||
// Peek attempts to return the node with either the largest or smallest
|
||||
// average, depending on if this is a max heap or a min heap, respectively.
|
||||
Peek() (ids.NodeID, Averager, bool)
|
||||
// Len returns the number of nodes that are currently in the heap.
|
||||
Len() int
|
||||
}
|
||||
|
||||
type averagerHeap struct {
|
||||
heap heap.Map[ids.NodeID, Averager]
|
||||
}
|
||||
|
||||
// NewMaxAveragerHeap returns a new empty max heap. The returned heap is not
|
||||
// thread safe.
|
||||
func NewMaxAveragerHeap() AveragerHeap {
|
||||
return averagerHeap{
|
||||
heap: heap.NewMap[ids.NodeID, Averager](func(a, b Averager) bool {
|
||||
return a.Read() > b.Read()
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
func (h averagerHeap) Add(nodeID ids.NodeID, averager Averager) (Averager, bool) {
|
||||
return h.heap.Push(nodeID, averager)
|
||||
}
|
||||
|
||||
func (h averagerHeap) Remove(nodeID ids.NodeID) (Averager, bool) {
|
||||
return h.heap.Remove(nodeID)
|
||||
}
|
||||
|
||||
func (h averagerHeap) Pop() (ids.NodeID, Averager, bool) {
|
||||
return h.heap.Pop()
|
||||
}
|
||||
|
||||
func (h averagerHeap) Peek() (ids.NodeID, Averager, bool) {
|
||||
return h.heap.Peek()
|
||||
}
|
||||
|
||||
func (h averagerHeap) Len() int {
|
||||
return h.heap.Len()
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
// Copyright (C) 2019-2024, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package math
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/luxfi/ids"
|
||||
)
|
||||
|
||||
func TestAveragerHeap(t *testing.T) {
|
||||
n0 := ids.GenerateTestNodeID()
|
||||
n1 := ids.GenerateTestNodeID()
|
||||
n2 := ids.GenerateTestNodeID()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
h AveragerHeap
|
||||
a []Averager
|
||||
}{
|
||||
{
|
||||
name: "max heap",
|
||||
h: NewMaxAveragerHeap(),
|
||||
a: []Averager{
|
||||
NewAverager(0, time.Second, time.Now()),
|
||||
NewAverager(-1, time.Second, time.Now()),
|
||||
NewAverager(-2, time.Second, time.Now()),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
_, _, ok := test.h.Pop()
|
||||
require.False(ok)
|
||||
|
||||
_, _, ok = test.h.Peek()
|
||||
require.False(ok)
|
||||
|
||||
l := test.h.Len()
|
||||
require.Zero(l)
|
||||
|
||||
_, ok = test.h.Add(n1, test.a[1])
|
||||
require.False(ok)
|
||||
|
||||
n, a, ok := test.h.Peek()
|
||||
require.True(ok)
|
||||
require.Equal(n1, n)
|
||||
require.Equal(test.a[1], a)
|
||||
|
||||
l = test.h.Len()
|
||||
require.Equal(1, l)
|
||||
|
||||
a, ok = test.h.Add(n1, test.a[1])
|
||||
require.True(ok)
|
||||
require.Equal(test.a[1], a)
|
||||
|
||||
l = test.h.Len()
|
||||
require.Equal(1, l)
|
||||
|
||||
_, ok = test.h.Add(n0, test.a[0])
|
||||
require.False(ok)
|
||||
|
||||
_, ok = test.h.Add(n2, test.a[2])
|
||||
require.False(ok)
|
||||
|
||||
n, a, ok = test.h.Pop()
|
||||
require.True(ok)
|
||||
require.Equal(n0, n)
|
||||
require.Equal(test.a[0], a)
|
||||
|
||||
l = test.h.Len()
|
||||
require.Equal(2, l)
|
||||
|
||||
a, ok = test.h.Remove(n1)
|
||||
require.True(ok)
|
||||
require.Equal(test.a[1], a)
|
||||
|
||||
l = test.h.Len()
|
||||
require.Equal(1, l)
|
||||
|
||||
_, ok = test.h.Remove(n1)
|
||||
require.False(ok)
|
||||
|
||||
l = test.h.Len()
|
||||
require.Equal(1, l)
|
||||
|
||||
a, ok = test.h.Add(n2, test.a[0])
|
||||
require.True(ok)
|
||||
require.Equal(test.a[2], a)
|
||||
|
||||
n, a, ok = test.h.Pop()
|
||||
require.True(ok)
|
||||
require.Equal(n2, n)
|
||||
require.Equal(test.a[0], a)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
// Copyright (C) 2019-2024, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package math
|
||||
|
||||
import (
|
||||
"math"
|
||||
"time"
|
||||
)
|
||||
|
||||
var convertEToBase2 = math.Log(2)
|
||||
|
||||
type continuousAverager struct {
|
||||
halflife float64
|
||||
weightedSum float64
|
||||
normalizer float64
|
||||
lastUpdated time.Time
|
||||
}
|
||||
|
||||
// NewUninitializedAverager creates a new averager with the given halflife. If
|
||||
// [Read] is called before [Observe], the zero value will be returned. When
|
||||
// [Observe] is called the first time, the averager will be initialized with
|
||||
// [value] at that time.
|
||||
func NewUninitializedAverager(halfLife time.Duration) Averager {
|
||||
// Use 0 as the initialPrediction and 0 as the currentTime, so that when the
|
||||
// first observation occurs (at a non-zero time) the initial prediction's
|
||||
// weight will become negligible.
|
||||
return NewAverager(0, halfLife, time.Time{})
|
||||
}
|
||||
|
||||
func NewAverager(
|
||||
initialPrediction float64,
|
||||
halflife time.Duration,
|
||||
currentTime time.Time,
|
||||
) Averager {
|
||||
return &continuousAverager{
|
||||
halflife: float64(halflife) / convertEToBase2,
|
||||
weightedSum: initialPrediction,
|
||||
normalizer: 1,
|
||||
lastUpdated: currentTime,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *continuousAverager) Observe(value float64, currentTime time.Time) {
|
||||
delta := a.lastUpdated.Sub(currentTime)
|
||||
switch {
|
||||
case delta < 0:
|
||||
// If the times are called in order, scale the previous values to keep the
|
||||
// sizes manageable
|
||||
newWeight := math.Exp(float64(delta) / a.halflife)
|
||||
|
||||
a.weightedSum = value + newWeight*a.weightedSum
|
||||
a.normalizer = 1 + newWeight*a.normalizer
|
||||
|
||||
a.lastUpdated = currentTime
|
||||
case delta == 0:
|
||||
// If this is called multiple times at the same wall clock time, no
|
||||
// scaling needs to occur
|
||||
a.weightedSum += value
|
||||
a.normalizer++
|
||||
default:
|
||||
// If the times are called out of order, don't scale the previous values
|
||||
newWeight := math.Exp(float64(-delta) / a.halflife)
|
||||
|
||||
a.weightedSum += newWeight * value
|
||||
a.normalizer += newWeight
|
||||
}
|
||||
}
|
||||
|
||||
func (a *continuousAverager) Read() float64 {
|
||||
return a.weightedSum / a.normalizer
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
// Copyright (C) 2019-2024, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package math
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func BenchmarkAveragers(b *testing.B) {
|
||||
periods := []time.Duration{
|
||||
time.Millisecond,
|
||||
time.Duration(0),
|
||||
-time.Millisecond,
|
||||
}
|
||||
for _, period := range periods {
|
||||
name := fmt.Sprintf("period=%s", period)
|
||||
b.Run(name, func(b *testing.B) {
|
||||
a := NewAverager(0, time.Second, time.Now())
|
||||
AveragerBenchmark(b, a, period)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func AveragerBenchmark(b *testing.B, a Averager, period time.Duration) {
|
||||
currentTime := time.Now()
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
currentTime = currentTime.Add(period)
|
||||
a.Observe(float64(i), currentTime)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
// Copyright (C) 2019-2024, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package math
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestAverager(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
halflife := time.Second
|
||||
currentTime := time.Now()
|
||||
|
||||
a := NewSyncAverager(NewAverager(0, halflife, currentTime))
|
||||
require.Zero(a.Read())
|
||||
|
||||
currentTime = currentTime.Add(halflife)
|
||||
a.Observe(1, currentTime)
|
||||
require.Equal(1.0/1.5, a.Read())
|
||||
}
|
||||
|
||||
func TestAveragerTimeTravel(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
halflife := time.Second
|
||||
currentTime := time.Now()
|
||||
|
||||
a := NewSyncAverager(NewAverager(1, halflife, currentTime))
|
||||
require.Equal(float64(1), a.Read())
|
||||
|
||||
currentTime = currentTime.Add(-halflife)
|
||||
a.Observe(0, currentTime)
|
||||
require.Equal(1.0/1.5, a.Read())
|
||||
}
|
||||
|
||||
func TestUninitializedAverager(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
halfLife := time.Second
|
||||
currentTime := time.Now()
|
||||
|
||||
firstObservation := float64(10)
|
||||
|
||||
a := NewUninitializedAverager(halfLife)
|
||||
require.Zero(a.Read())
|
||||
|
||||
a.Observe(firstObservation, currentTime)
|
||||
require.Equal(firstObservation, a.Read())
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
// Copyright (C) 2019-2024, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package meter
|
||||
|
||||
import (
|
||||
"math"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
convertEToBase2 = math.Log(2)
|
||||
|
||||
_ Factory = (*ContinuousFactory)(nil)
|
||||
_ Meter = (*continuousMeter)(nil)
|
||||
)
|
||||
|
||||
// ContinuousFactory implements the Factory interface by returning a continuous
|
||||
// time meter.
|
||||
type ContinuousFactory struct{}
|
||||
|
||||
func (ContinuousFactory) New(halflife time.Duration) Meter {
|
||||
return NewMeter(halflife)
|
||||
}
|
||||
|
||||
type continuousMeter struct {
|
||||
halflife float64
|
||||
value float64
|
||||
|
||||
numCoresRunning float64
|
||||
lastUpdated time.Time
|
||||
}
|
||||
|
||||
// NewMeter returns a new Meter with the provided halflife
|
||||
func NewMeter(halflife time.Duration) Meter {
|
||||
return &continuousMeter{
|
||||
halflife: float64(halflife) / convertEToBase2,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *continuousMeter) Inc(now time.Time, numCores float64) {
|
||||
a.Read(now)
|
||||
a.numCoresRunning += numCores
|
||||
}
|
||||
|
||||
func (a *continuousMeter) Dec(now time.Time, numCores float64) {
|
||||
a.Read(now)
|
||||
a.numCoresRunning -= numCores
|
||||
}
|
||||
|
||||
func (a *continuousMeter) Read(now time.Time) float64 {
|
||||
timeSincePreviousUpdate := a.lastUpdated.Sub(now)
|
||||
if timeSincePreviousUpdate >= 0 {
|
||||
return a.value
|
||||
}
|
||||
a.lastUpdated = now
|
||||
|
||||
factor := math.Exp(float64(timeSincePreviousUpdate) / a.halflife)
|
||||
a.value *= factor
|
||||
a.value += a.numCoresRunning * (1 - factor)
|
||||
return a.value
|
||||
}
|
||||
|
||||
func (a *continuousMeter) TimeUntil(now time.Time, value float64) time.Duration {
|
||||
currentValue := a.Read(now)
|
||||
if currentValue <= value {
|
||||
return time.Duration(0)
|
||||
}
|
||||
// Note that [factor] >= 1
|
||||
factor := currentValue / value
|
||||
// Note that [numHalfLives] >= 0
|
||||
numHalflives := math.Log(factor)
|
||||
duration := numHalflives * a.halflife
|
||||
// Overflow protection
|
||||
if duration > math.MaxInt64 {
|
||||
return time.Duration(math.MaxInt64)
|
||||
}
|
||||
return time.Duration(duration)
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// Copyright (C) 2019-2024, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package meter
|
||||
|
||||
import "time"
|
||||
|
||||
// Factory returns new meters.
|
||||
type Factory interface {
|
||||
// New returns a new meter with the provided halflife.
|
||||
New(halflife time.Duration) Meter
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// Copyright (C) 2019-2024, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package meter
|
||||
|
||||
import "time"
|
||||
|
||||
// Meter tracks a continuous exponential moving average of the % of time this
|
||||
// meter has been running.
|
||||
type Meter interface {
|
||||
// Inc the meter, the read value will be monotonically increasing while
|
||||
// the meter is running.
|
||||
Inc(time.Time, float64)
|
||||
|
||||
// Dec the meter, the read value will be exponentially decreasing while the
|
||||
// meter is off.
|
||||
Dec(time.Time, float64)
|
||||
|
||||
// Read the current value of the meter, this can be used to approximate the
|
||||
// percent of time the meter has been running recently. The definition of
|
||||
// recently depends on the halflife of the decay function.
|
||||
Read(time.Time) float64
|
||||
|
||||
// Returns the duration between [now] and when the value of this meter
|
||||
// reaches [value], assuming that the number of cores running is always 0.
|
||||
// If the value of this meter is already <= [value], returns the zero duration.
|
||||
TimeUntil(now time.Time, value float64) time.Duration
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
// Copyright (C) 2019-2024, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package meter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func BenchmarkMeters(b *testing.B) {
|
||||
for _, meterDef := range meters {
|
||||
period := time.Second + 500*time.Millisecond
|
||||
name := fmt.Sprintf("%s-%s", meterDef.name, period)
|
||||
b.Run(name, func(b *testing.B) {
|
||||
m := meterDef.factory.New(halflife)
|
||||
MeterBenchmark(b, m, period)
|
||||
})
|
||||
|
||||
period = time.Millisecond
|
||||
name = fmt.Sprintf("%s-%s", meterDef.name, period)
|
||||
b.Run(name, func(b *testing.B) {
|
||||
m := meterDef.factory.New(halflife)
|
||||
MeterBenchmark(b, m, period)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func MeterBenchmark(b *testing.B, m Meter, period time.Duration) {
|
||||
currentTime := time.Now()
|
||||
m.Inc(currentTime, 1)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
currentTime = currentTime.Add(period)
|
||||
m.Read(currentTime)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
// Copyright (C) 2019-2024, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package meter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var (
|
||||
halflife = time.Second
|
||||
meters = []struct {
|
||||
name string
|
||||
factory Factory
|
||||
}{
|
||||
{
|
||||
name: "continuous",
|
||||
factory: ContinuousFactory{},
|
||||
},
|
||||
}
|
||||
|
||||
meterTests = []struct {
|
||||
name string
|
||||
test func(*testing.T, Factory)
|
||||
}{
|
||||
{
|
||||
name: "new",
|
||||
test: NewTest,
|
||||
},
|
||||
{
|
||||
name: "standard usage",
|
||||
test: StandardUsageTest,
|
||||
},
|
||||
{
|
||||
name: "time travel",
|
||||
test: TimeTravelTest,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func TestMeters(t *testing.T) {
|
||||
for _, s := range meters {
|
||||
for _, test := range meterTests {
|
||||
t.Run(fmt.Sprintf("meter %s test %s", s.name, test.name), func(t *testing.T) {
|
||||
test.test(t, s.factory)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func NewTest(t *testing.T, factory Factory) {
|
||||
require.NotNil(t, factory.New(halflife))
|
||||
}
|
||||
|
||||
func TimeTravelTest(t *testing.T, factory Factory) {
|
||||
require := require.New(t)
|
||||
|
||||
m := factory.New(halflife)
|
||||
|
||||
now := time.Date(1, 2, 3, 4, 5, 6, 7, time.UTC)
|
||||
m.Inc(now, 1)
|
||||
|
||||
now = now.Add(halflife - 1)
|
||||
delta := 0.0001
|
||||
require.InDelta(.5, m.Read(now), delta)
|
||||
|
||||
m.Dec(now, 1)
|
||||
|
||||
now = now.Add(-halflife)
|
||||
require.InDelta(.5, m.Read(now), delta)
|
||||
|
||||
m.Inc(now, 1)
|
||||
|
||||
now = now.Add(halflife / 2)
|
||||
require.InDelta(.5, m.Read(now), delta)
|
||||
}
|
||||
|
||||
func StandardUsageTest(t *testing.T, factory Factory) {
|
||||
require := require.New(t)
|
||||
|
||||
m := factory.New(halflife)
|
||||
|
||||
now := time.Date(1, 2, 3, 4, 5, 6, 7, time.UTC)
|
||||
m.Inc(now, 1)
|
||||
|
||||
now = now.Add(halflife - 1)
|
||||
delta := 0.0001
|
||||
require.InDelta(.5, m.Read(now), delta)
|
||||
|
||||
m.Inc(now, 1)
|
||||
require.InDelta(.5, m.Read(now), delta)
|
||||
|
||||
m.Dec(now, 1)
|
||||
require.InDelta(.5, m.Read(now), delta)
|
||||
|
||||
m.Dec(now, 1)
|
||||
|
||||
require.InDelta(.5, m.Read(now), delta)
|
||||
|
||||
now = now.Add(halflife)
|
||||
require.InDelta(.25, m.Read(now), delta)
|
||||
|
||||
m.Inc(now, 1)
|
||||
|
||||
now = now.Add(halflife)
|
||||
require.InDelta(.625, m.Read(now), delta)
|
||||
|
||||
now = now.Add(34 * halflife)
|
||||
require.InDelta(1, m.Read(now), delta)
|
||||
|
||||
m.Dec(now, 1)
|
||||
|
||||
now = now.Add(34 * halflife)
|
||||
require.InDelta(0, m.Read(now), delta)
|
||||
|
||||
m.Inc(now, 1)
|
||||
|
||||
now = now.Add(2 * halflife)
|
||||
require.InDelta(.75, m.Read(now), delta)
|
||||
|
||||
// Second start
|
||||
m.Inc(now, 1)
|
||||
|
||||
now = now.Add(34 * halflife)
|
||||
require.InDelta(2, m.Read(now), delta)
|
||||
|
||||
// Stop the second CPU
|
||||
m.Dec(now, 1)
|
||||
|
||||
now = now.Add(34 * halflife)
|
||||
require.InDelta(1, m.Read(now), delta)
|
||||
}
|
||||
|
||||
func TestTimeUntil(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
halflife := 5 * time.Second
|
||||
f := ContinuousFactory{}
|
||||
m := f.New(halflife)
|
||||
now := time.Now()
|
||||
// Start the meter
|
||||
m.Inc(now, 1)
|
||||
// One halflife passes; stop the meter
|
||||
now = now.Add(halflife)
|
||||
m.Dec(now, 1)
|
||||
// Read the current value
|
||||
currentVal := m.Read(now)
|
||||
// Suppose we want to wait for the value to be
|
||||
// a third of its current value
|
||||
desiredVal := currentVal / 3
|
||||
// See when that should happen
|
||||
timeUntilDesiredVal := m.TimeUntil(now, desiredVal)
|
||||
// Get the actual value at that time
|
||||
now = now.Add(timeUntilDesiredVal)
|
||||
actualVal := m.Read(now)
|
||||
// Make sure the actual/expected are close
|
||||
require.InDelta(desiredVal, actualVal, .00001)
|
||||
// Make sure TimeUntil returns the zero duration if
|
||||
// the value provided >= the current value
|
||||
require.Zero(m.TimeUntil(now, actualVal))
|
||||
require.Zero(m.TimeUntil(now, actualVal+.1))
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
// Copyright (C) 2019-2024, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package math
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math"
|
||||
|
||||
"golang.org/x/exp/constraints"
|
||||
|
||||
"github.com/luxfi/node/utils"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrOverflow = errors.New("overflow")
|
||||
ErrUnderflow = errors.New("underflow")
|
||||
)
|
||||
|
||||
// Add64 returns:
|
||||
// 1) a + b
|
||||
// 2) If there is overflow, an error
|
||||
//
|
||||
// Note that we don't have a generic Add function because checking for
|
||||
// an overflow requires knowing the max size of a given type, which we
|
||||
// don't know if we're adding generic types.
|
||||
func Add64(a, b uint64) (uint64, error) {
|
||||
if a > math.MaxUint64-b {
|
||||
return 0, ErrOverflow
|
||||
}
|
||||
return a + b, nil
|
||||
}
|
||||
|
||||
// Sub returns:
|
||||
// 1) a - b
|
||||
// 2) If there is underflow, an error
|
||||
func Sub[T constraints.Unsigned](a, b T) (T, error) {
|
||||
if a < b {
|
||||
return utils.Zero[T](), ErrUnderflow
|
||||
}
|
||||
return a - b, nil
|
||||
}
|
||||
|
||||
// Mul64 returns:
|
||||
// 1) a * b
|
||||
// 2) If there is overflow, an error
|
||||
//
|
||||
// Note that we don't have a generic Mul function because checking for
|
||||
// an overflow requires knowing the max size of a given type, which we
|
||||
// don't know if we're adding generic types.
|
||||
func Mul64(a, b uint64) (uint64, error) {
|
||||
if b != 0 && a > math.MaxUint64/b {
|
||||
return 0, ErrOverflow
|
||||
}
|
||||
return a * b, nil
|
||||
}
|
||||
|
||||
func AbsDiff[T constraints.Unsigned](a, b T) T {
|
||||
return max(a, b) - min(a, b)
|
||||
}
|
||||
|
||||
// Min returns the minimum of two uint64 values
|
||||
func Min(a, b uint64) uint64 {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// Max returns the maximum of two uint64 values
|
||||
func Max(a, b uint64) uint64 {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
// Copyright (C) 2019-2024, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package math
|
||||
|
||||
import (
|
||||
"math"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const maxUint64 uint64 = math.MaxUint64
|
||||
|
||||
func TestAdd64(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
sum, err := Add64(0, maxUint64)
|
||||
require.NoError(err)
|
||||
require.Equal(maxUint64, sum)
|
||||
|
||||
sum, err = Add64(maxUint64, 0)
|
||||
require.NoError(err)
|
||||
require.Equal(maxUint64, sum)
|
||||
|
||||
sum, err = Add64(uint64(1<<62), uint64(1<<62))
|
||||
require.NoError(err)
|
||||
require.Equal(uint64(1<<63), sum)
|
||||
|
||||
_, err = Add64(1, maxUint64)
|
||||
require.ErrorIs(err, ErrOverflow)
|
||||
|
||||
_, err = Add64(maxUint64, 1)
|
||||
require.ErrorIs(err, ErrOverflow)
|
||||
|
||||
_, err = Add64(maxUint64, maxUint64)
|
||||
require.ErrorIs(err, ErrOverflow)
|
||||
}
|
||||
|
||||
func TestSub(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
got, err := Sub(uint64(2), uint64(1))
|
||||
require.NoError(err)
|
||||
require.Equal(uint64(1), got)
|
||||
|
||||
got, err = Sub(uint64(2), uint64(2))
|
||||
require.NoError(err)
|
||||
require.Zero(got)
|
||||
|
||||
got, err = Sub(maxUint64, maxUint64)
|
||||
require.NoError(err)
|
||||
require.Zero(got)
|
||||
|
||||
got, err = Sub(uint64(3), uint64(2))
|
||||
require.NoError(err)
|
||||
require.Equal(uint64(1), got)
|
||||
|
||||
_, err = Sub(uint64(1), uint64(2))
|
||||
require.ErrorIs(err, ErrUnderflow)
|
||||
|
||||
_, err = Sub(maxUint64-1, maxUint64)
|
||||
require.ErrorIs(err, ErrUnderflow)
|
||||
}
|
||||
|
||||
func TestMul64(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
got, err := Mul64(0, maxUint64)
|
||||
require.NoError(err)
|
||||
require.Zero(got)
|
||||
|
||||
got, err = Mul64(maxUint64, 0)
|
||||
require.NoError(err)
|
||||
require.Zero(got)
|
||||
|
||||
got, err = Mul64(uint64(1), uint64(3))
|
||||
require.NoError(err)
|
||||
require.Equal(uint64(3), got)
|
||||
|
||||
got, err = Mul64(uint64(3), uint64(1))
|
||||
require.NoError(err)
|
||||
require.Equal(uint64(3), got)
|
||||
|
||||
got, err = Mul64(uint64(2), uint64(3))
|
||||
require.NoError(err)
|
||||
require.Equal(uint64(6), got)
|
||||
|
||||
got, err = Mul64(maxUint64, 0)
|
||||
require.NoError(err)
|
||||
require.Zero(got)
|
||||
|
||||
_, err = Mul64(maxUint64-1, 2)
|
||||
require.ErrorIs(err, ErrOverflow)
|
||||
}
|
||||
|
||||
func TestAbsDiff(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
require.Equal(maxUint64, AbsDiff(0, maxUint64))
|
||||
require.Equal(maxUint64, AbsDiff(maxUint64, 0))
|
||||
require.Equal(uint64(2), AbsDiff(uint64(3), uint64(1)))
|
||||
require.Equal(uint64(2), AbsDiff(uint64(1), uint64(3)))
|
||||
require.Zero(AbsDiff(uint64(1), uint64(1)))
|
||||
require.Zero(AbsDiff(uint64(0), uint64(0)))
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
// Copyright (C) 2019-2024, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package math
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type syncAverager struct {
|
||||
lock sync.RWMutex
|
||||
averager Averager
|
||||
}
|
||||
|
||||
func NewSyncAverager(averager Averager) Averager {
|
||||
return &syncAverager{
|
||||
averager: averager,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *syncAverager) Observe(value float64, currentTime time.Time) {
|
||||
a.lock.Lock()
|
||||
defer a.lock.Unlock()
|
||||
|
||||
a.averager.Observe(value, currentTime)
|
||||
}
|
||||
|
||||
func (a *syncAverager) Read() float64 {
|
||||
a.lock.RLock()
|
||||
defer a.lock.RUnlock()
|
||||
|
||||
return a.averager.Read()
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package math
|
||||
|
||||
// Re-export commonly used math functions from github.com/luxfi/geth/common/math
|
||||
import (
|
||||
"math/big"
|
||||
|
||||
"github.com/luxfi/geth/common/math"
|
||||
)
|
||||
|
||||
var (
|
||||
BigPow = math.BigPow
|
||||
)
|
||||
|
||||
// BigMax returns the larger of x or y.
|
||||
func BigMax(x, y *big.Int) *big.Int {
|
||||
if x.Cmp(y) < 0 {
|
||||
return y
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
// BigMin returns the smaller of x or y.
|
||||
func BigMin(x, y *big.Int) *big.Int {
|
||||
if x.Cmp(y) > 0 {
|
||||
return y
|
||||
}
|
||||
return x
|
||||
}
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
// Copyright (C) 2019-2024, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package set
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"math/big"
|
||||
"math/bits"
|
||||
)
|
||||
|
||||
// Bits is a bit-set backed by a big.Int
|
||||
// Holds values ranging from [0, INT_MAX] (arch-dependent)
|
||||
// Trying to use negative values will result in a panic.
|
||||
// This implementation is NOT thread-safe.
|
||||
type Bits struct {
|
||||
bits *big.Int
|
||||
}
|
||||
|
||||
// NewBits returns a new instance of Bits with [bits] set to 1.
|
||||
//
|
||||
// Invariants:
|
||||
// 1. Negative bits will cause a panic.
|
||||
// 2. Duplicate bits are allowed but will cause a no-op.
|
||||
func NewBits(bits ...int) Bits {
|
||||
b := Bits{new(big.Int)}
|
||||
for _, bit := range bits {
|
||||
b.Add(bit)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// Add sets the [i]'th bit to 1
|
||||
func (b Bits) Add(i int) {
|
||||
b.bits.SetBit(b.bits, i, 1)
|
||||
}
|
||||
|
||||
// Union performs the set union with another set.
|
||||
// This adds all elements in [other] to [b]
|
||||
func (b Bits) Union(other Bits) {
|
||||
b.bits.Or(b.bits, other.bits)
|
||||
}
|
||||
|
||||
// Intersection performs the set intersection with another set
|
||||
// This sets [b] to include only elements in both [b] and [other]
|
||||
func (b Bits) Intersection(other Bits) {
|
||||
b.bits.And(b.bits, other.bits)
|
||||
}
|
||||
|
||||
// Difference removes all the elements in [other] from this set
|
||||
func (b Bits) Difference(other Bits) {
|
||||
b.bits.AndNot(b.bits, other.bits)
|
||||
}
|
||||
|
||||
// Remove sets the [i]'th bit to 0
|
||||
func (b Bits) Remove(i int) {
|
||||
b.bits.SetBit(b.bits, i, 0)
|
||||
}
|
||||
|
||||
// Clear empties out the bitset
|
||||
func (b Bits) Clear() {
|
||||
b.bits.SetUint64(0)
|
||||
}
|
||||
|
||||
// Contains returns true if the [i]'th bit is 1, and false otherwise
|
||||
func (b Bits) Contains(i int) bool {
|
||||
return b.bits.Bit(i) == 1
|
||||
}
|
||||
|
||||
// BitLen returns the bit length of this bitset
|
||||
func (b Bits) BitLen() int {
|
||||
return b.bits.BitLen()
|
||||
}
|
||||
|
||||
// Len returns the amount of 1's in the bitset
|
||||
//
|
||||
// This is typically referred to as the "Hamming Weight"
|
||||
// of a set of bits.
|
||||
func (b Bits) Len() int {
|
||||
result := 0
|
||||
for _, word := range b.bits.Bits() {
|
||||
result += bits.OnesCount(uint(word))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Returns the byte representation of this bitset
|
||||
func (b Bits) Bytes() []byte {
|
||||
return b.bits.Bytes()
|
||||
}
|
||||
|
||||
// Inverse of Bits.Bytes()
|
||||
func BitsFromBytes(bytes []byte) Bits {
|
||||
return Bits{
|
||||
bits: new(big.Int).SetBytes(bytes),
|
||||
}
|
||||
}
|
||||
|
||||
// String returns the hex representation of this bitset
|
||||
func (b Bits) String() string {
|
||||
return hex.EncodeToString(b.bits.Bytes())
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
// Copyright (C) 2019-2024, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package set
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/bits"
|
||||
)
|
||||
|
||||
// Bits64 is a set that can contain uints in the range [0, 64). All functions
|
||||
// are O(1). The zero value is the empty set.
|
||||
type Bits64 uint64
|
||||
|
||||
// Add [i] to the set of ints
|
||||
func (b *Bits64) Add(i uint) {
|
||||
*b |= 1 << i
|
||||
}
|
||||
|
||||
// Union adds all the elements in [s] to this set
|
||||
func (b *Bits64) Union(s Bits64) {
|
||||
*b |= s
|
||||
}
|
||||
|
||||
// Intersection takes the intersection of [s] with this set
|
||||
func (b *Bits64) Intersection(s Bits64) {
|
||||
*b &= s
|
||||
}
|
||||
|
||||
// Difference removes all the elements in [s] from this set
|
||||
func (b *Bits64) Difference(s Bits64) {
|
||||
*b &^= s
|
||||
}
|
||||
|
||||
// Remove [i] from the set of ints
|
||||
func (b *Bits64) Remove(i uint) {
|
||||
*b &^= 1 << i
|
||||
}
|
||||
|
||||
// Clear removes all elements from this set
|
||||
func (b *Bits64) Clear() {
|
||||
*b = 0
|
||||
}
|
||||
|
||||
// Contains returns true if [i] was previously added to this set
|
||||
func (b Bits64) Contains(i uint) bool {
|
||||
return b&(1<<i) != 0
|
||||
}
|
||||
|
||||
// Len returns the number of elements in this set
|
||||
func (b Bits64) Len() int {
|
||||
return bits.OnesCount64(uint64(b))
|
||||
}
|
||||
|
||||
func (b Bits64) String() string {
|
||||
return fmt.Sprintf("%016x", uint64(b))
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
// Copyright (C) 2019-2024, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package set
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestBits64(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
var bs1 Bits64
|
||||
require.Empty(bs1)
|
||||
|
||||
bs1.Add(5)
|
||||
require.Equal(1, bs1.Len())
|
||||
require.True(bs1.Contains(5))
|
||||
|
||||
bs1.Add(10)
|
||||
require.Equal(2, bs1.Len())
|
||||
require.True(bs1.Contains(5))
|
||||
require.True(bs1.Contains(10))
|
||||
|
||||
bs1.Add(10)
|
||||
require.Equal(2, bs1.Len())
|
||||
require.True(bs1.Contains(5))
|
||||
require.True(bs1.Contains(10))
|
||||
|
||||
var bs2 Bits64
|
||||
require.Empty(bs2)
|
||||
|
||||
bs2.Add(0)
|
||||
require.Equal(1, bs2.Len())
|
||||
require.True(bs2.Contains(0))
|
||||
|
||||
bs2.Union(bs1)
|
||||
require.Equal(2, bs1.Len())
|
||||
require.True(bs1.Contains(5))
|
||||
require.True(bs1.Contains(10))
|
||||
require.Equal(3, bs2.Len())
|
||||
require.True(bs2.Contains(0))
|
||||
require.True(bs2.Contains(5))
|
||||
require.True(bs2.Contains(10))
|
||||
|
||||
bs1.Clear()
|
||||
require.Empty(bs1)
|
||||
require.Equal(3, bs2.Len())
|
||||
require.True(bs2.Contains(0))
|
||||
require.True(bs2.Contains(5))
|
||||
require.True(bs2.Contains(10))
|
||||
|
||||
bs1.Add(63)
|
||||
require.Equal(1, bs1.Len())
|
||||
require.True(bs1.Contains(63))
|
||||
|
||||
bs1.Add(1)
|
||||
require.Equal(2, bs1.Len())
|
||||
require.True(bs1.Contains(1))
|
||||
require.True(bs1.Contains(63))
|
||||
|
||||
bs1.Remove(63)
|
||||
require.Equal(1, bs1.Len())
|
||||
require.True(bs1.Contains(1))
|
||||
|
||||
var bs3 Bits64
|
||||
require.Empty(bs3)
|
||||
|
||||
bs3.Add(0)
|
||||
bs3.Add(2)
|
||||
bs3.Add(5)
|
||||
|
||||
var bs4 Bits64
|
||||
require.Empty(bs4)
|
||||
|
||||
bs4.Add(2)
|
||||
bs4.Add(5)
|
||||
|
||||
bs3.Intersection(bs4)
|
||||
|
||||
require.Equal(2, bs3.Len())
|
||||
require.True(bs3.Contains(2))
|
||||
require.True(bs3.Contains(5))
|
||||
require.Equal(2, bs4.Len())
|
||||
require.True(bs4.Contains(2))
|
||||
require.True(bs4.Contains(5))
|
||||
|
||||
var bs5 Bits64
|
||||
require.Empty(bs5)
|
||||
|
||||
bs5.Add(7)
|
||||
bs5.Add(11)
|
||||
bs5.Add(9)
|
||||
|
||||
var bs6 Bits64
|
||||
require.Empty(bs6)
|
||||
|
||||
bs6.Add(9)
|
||||
bs6.Add(11)
|
||||
|
||||
bs5.Difference(bs6)
|
||||
require.Equal(1, bs5.Len())
|
||||
require.True(bs5.Contains(7))
|
||||
require.Equal(2, bs6.Len())
|
||||
require.True(bs6.Contains(9))
|
||||
require.True(bs6.Contains(11))
|
||||
}
|
||||
|
||||
func TestBits64String(t *testing.T) {
|
||||
var bs Bits64
|
||||
|
||||
bs.Add(17)
|
||||
|
||||
require.Equal(t, "0000000000020000", bs.String())
|
||||
}
|
||||
@@ -0,0 +1,508 @@
|
||||
// Copyright (C) 2019-2024, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package set
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_Bits_New(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
bits []int
|
||||
length int
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
bits: []int{},
|
||||
length: 0,
|
||||
},
|
||||
{
|
||||
name: "populated",
|
||||
bits: []int{0, 9, 99, 999, 9999},
|
||||
length: 10_000,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
require := require.New(t)
|
||||
b := NewBits(test.bits...)
|
||||
|
||||
for _, bit := range test.bits {
|
||||
require.True(b.Contains(bit))
|
||||
}
|
||||
|
||||
require.Equal(test.length, b.BitLen())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Bits_AddRemove(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
toAdd []int
|
||||
toRemove []int
|
||||
expectedElements []int
|
||||
expectedLen int
|
||||
}{
|
||||
{
|
||||
name: "empty sets",
|
||||
toAdd: []int{},
|
||||
toRemove: []int{},
|
||||
expectedElements: []int{}, // []
|
||||
expectedLen: 0,
|
||||
},
|
||||
{
|
||||
name: "add only",
|
||||
toAdd: []int{0, 1, 2},
|
||||
toRemove: []int{},
|
||||
expectedElements: []int{0, 1, 2}, // [1, 1, 1]
|
||||
expectedLen: 3,
|
||||
},
|
||||
{
|
||||
name: "remove left-most",
|
||||
toAdd: []int{0, 1, 2},
|
||||
toRemove: []int{0},
|
||||
expectedElements: []int{1, 2}, // [1, 1, 0]
|
||||
expectedLen: 3,
|
||||
},
|
||||
{
|
||||
name: "remove middle",
|
||||
toAdd: []int{0, 1, 2},
|
||||
toRemove: []int{1},
|
||||
expectedElements: []int{2, 0}, // [1, 0, 1]
|
||||
expectedLen: 3,
|
||||
},
|
||||
{
|
||||
name: "remove right-most",
|
||||
toAdd: []int{0, 1, 2},
|
||||
toRemove: []int{2},
|
||||
expectedElements: []int{0, 1}, // [1, 1]
|
||||
expectedLen: 2,
|
||||
},
|
||||
{
|
||||
name: "remove all",
|
||||
toAdd: []int{0, 1, 2},
|
||||
toRemove: []int{0, 1, 2},
|
||||
expectedElements: []int{}, // [1, 1, 1]
|
||||
expectedLen: 0,
|
||||
},
|
||||
{
|
||||
name: "remove reverse-order",
|
||||
toAdd: []int{0, 1, 2},
|
||||
toRemove: []int{2, 1, 0},
|
||||
expectedElements: []int{}, // []
|
||||
expectedLen: 0,
|
||||
},
|
||||
{
|
||||
name: "remove non-existent elements",
|
||||
toAdd: []int{0, 1, 2},
|
||||
toRemove: []int{3, 4, 5},
|
||||
expectedElements: []int{0, 1, 2}, // [1, 1, 1]
|
||||
expectedLen: 3,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
require := require.New(t)
|
||||
b := NewBits()
|
||||
|
||||
for _, add := range test.toAdd {
|
||||
b.Add(add)
|
||||
}
|
||||
|
||||
for _, remove := range test.toRemove {
|
||||
b.Remove(remove)
|
||||
}
|
||||
|
||||
for _, element := range test.expectedElements {
|
||||
require.True(b.Contains(element))
|
||||
}
|
||||
|
||||
require.Equal(test.expectedLen, b.BitLen())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Bits_Union(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
left []int
|
||||
right []int
|
||||
expected []int
|
||||
expectedLen int
|
||||
}{
|
||||
{
|
||||
name: "empty sets",
|
||||
left: []int{},
|
||||
right: []int{},
|
||||
expected: []int{}, // []
|
||||
expectedLen: 0,
|
||||
},
|
||||
{
|
||||
name: "left and right are same",
|
||||
left: []int{2, 1, 0},
|
||||
right: []int{2, 1, 0},
|
||||
expected: []int{2, 1, 0}, // [1, 1, 1]
|
||||
expectedLen: 3,
|
||||
},
|
||||
{
|
||||
name: "left and no right",
|
||||
left: []int{2, 1, 0},
|
||||
right: []int{},
|
||||
expected: []int{2, 1, 0}, // [1, 1, 1]
|
||||
expectedLen: 3,
|
||||
},
|
||||
{
|
||||
name: "right and no left",
|
||||
left: []int{},
|
||||
right: []int{2, 1, 0},
|
||||
expected: []int{2, 1, 0}, // [1, 1, 1]
|
||||
expectedLen: 3,
|
||||
},
|
||||
{
|
||||
name: "left and right overlap",
|
||||
left: []int{2, 1},
|
||||
right: []int{1, 0},
|
||||
expected: []int{2, 1, 0}, // [1, 1, 1]
|
||||
expectedLen: 3,
|
||||
},
|
||||
{
|
||||
name: "left and right overlap different sizes",
|
||||
left: []int{5, 3, 1},
|
||||
right: []int{8, 6, 4, 2, 0},
|
||||
expected: []int{8, 6, 5, 4, 3, 2, 1, 0}, // [1, 0, 1, 1, 1, 1, 1, 1, 1]
|
||||
expectedLen: 9,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
require := require.New(t)
|
||||
b := NewBits()
|
||||
|
||||
for _, add := range test.left {
|
||||
b.Add(add)
|
||||
}
|
||||
for _, add := range test.right {
|
||||
b.Add(add)
|
||||
}
|
||||
|
||||
for _, element := range test.expected {
|
||||
require.True(b.Contains(element))
|
||||
}
|
||||
|
||||
require.Equal(test.expectedLen, b.BitLen())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Bits_Intersection(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
left []int
|
||||
right []int
|
||||
expected []int
|
||||
expectedLen int
|
||||
}{
|
||||
{
|
||||
name: "empty sets",
|
||||
left: []int{},
|
||||
right: []int{},
|
||||
expected: []int{}, // []
|
||||
expectedLen: 0,
|
||||
},
|
||||
{
|
||||
name: "left and right are same",
|
||||
left: []int{2, 1, 0},
|
||||
right: []int{2, 1, 0},
|
||||
expected: []int{2, 1, 0}, // [1, 1, 1]
|
||||
expectedLen: 3,
|
||||
},
|
||||
{
|
||||
name: "left and no right",
|
||||
left: []int{2, 1, 0},
|
||||
right: []int{},
|
||||
expected: []int{}, // []
|
||||
expectedLen: 0,
|
||||
},
|
||||
{
|
||||
name: "right and no left",
|
||||
left: []int{},
|
||||
right: []int{2, 1, 0},
|
||||
expected: []int{}, // []
|
||||
expectedLen: 0,
|
||||
},
|
||||
{
|
||||
name: "left and right overlap",
|
||||
left: []int{2, 1},
|
||||
right: []int{1, 0},
|
||||
expected: []int{1}, // [1, 0]
|
||||
expectedLen: 2,
|
||||
},
|
||||
{
|
||||
name: "left and right overlap different sizes",
|
||||
left: []int{5, 3, 1},
|
||||
right: []int{8, 6, 4, 2, 0},
|
||||
expected: []int{}, // []
|
||||
expectedLen: 0,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
require := require.New(t)
|
||||
left := NewBits()
|
||||
right := NewBits()
|
||||
for _, add := range test.left {
|
||||
left.Add(add)
|
||||
}
|
||||
for _, add := range test.right {
|
||||
right.Add(add)
|
||||
}
|
||||
|
||||
left.Intersection(right)
|
||||
|
||||
expected := NewBits()
|
||||
for _, element := range test.expected {
|
||||
expected.Add(element)
|
||||
}
|
||||
|
||||
require.ElementsMatch(left.bits.Bits(), expected.bits.Bits())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Bits_Difference(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
left []int
|
||||
right []int
|
||||
expected []int
|
||||
expectedLen int
|
||||
}{
|
||||
{
|
||||
name: "empty sets",
|
||||
left: []int{},
|
||||
right: []int{},
|
||||
expected: []int{}, // []
|
||||
expectedLen: 0,
|
||||
},
|
||||
{
|
||||
name: "left and right are same",
|
||||
left: []int{2, 1, 0},
|
||||
right: []int{2, 1, 0},
|
||||
expected: []int{}, // []
|
||||
expectedLen: 0,
|
||||
},
|
||||
{
|
||||
name: "left and no right",
|
||||
left: []int{2, 1, 0},
|
||||
right: []int{},
|
||||
expected: []int{2, 1, 0}, // [1, 1, 1]
|
||||
expectedLen: 3,
|
||||
},
|
||||
{
|
||||
name: "right and no left",
|
||||
left: []int{},
|
||||
right: []int{2, 1, 0},
|
||||
expected: []int{}, // []
|
||||
expectedLen: 3,
|
||||
},
|
||||
{
|
||||
name: "left and right overlap",
|
||||
left: []int{2, 1},
|
||||
right: []int{1, 0},
|
||||
expected: []int{2}, // [1, 0, 0]
|
||||
expectedLen: 3,
|
||||
},
|
||||
{
|
||||
name: "left and right overlap different sizes",
|
||||
left: []int{5, 3, 1},
|
||||
right: []int{8, 6, 4, 2, 0},
|
||||
expected: []int{5, 3, 1}, // [1, 0, 1, 0, 1, 0]
|
||||
expectedLen: 6,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
require := require.New(t)
|
||||
left := NewBits()
|
||||
right := NewBits()
|
||||
for _, add := range test.left {
|
||||
left.Add(add)
|
||||
}
|
||||
for _, add := range test.right {
|
||||
right.Add(add)
|
||||
}
|
||||
|
||||
left.Difference(right)
|
||||
|
||||
expected := NewBits()
|
||||
for _, element := range test.expected {
|
||||
expected.Add(element)
|
||||
}
|
||||
|
||||
require.ElementsMatch(left.bits.Bits(), expected.bits.Bits())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Bits_Clear(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
bitset []int
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
bitset: []int{}, // []
|
||||
},
|
||||
{
|
||||
name: "populated",
|
||||
bitset: []int{5, 4, 3, 2, 1}, // [1, 1, 1, 1, 1]
|
||||
},
|
||||
{
|
||||
name: "populated - big",
|
||||
bitset: []int{255}, // [1, 0...]
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
require := require.New(t)
|
||||
b := NewBits()
|
||||
|
||||
for bit := range test.bitset {
|
||||
b.Add(bit)
|
||||
}
|
||||
|
||||
b.Clear()
|
||||
|
||||
require.Zero(b.BitLen())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Bits_String(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
bitset []int
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
bitset: []int{},
|
||||
expected: "", // []
|
||||
},
|
||||
{
|
||||
name: "populated",
|
||||
bitset: []int{7, 6, 5, 4, 3, 2, 1, 0}, // [1, 1, 1, 1, 1, 1, 1, 1]
|
||||
expected: "ff",
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
require := require.New(t)
|
||||
b := NewBits()
|
||||
|
||||
for _, bit := range test.bitset {
|
||||
b.Add(bit)
|
||||
}
|
||||
|
||||
require.Equal(test.expected, b.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Bits_Len(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
bitset []int
|
||||
expected int
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
bitset: []int{}, // []
|
||||
expected: 0,
|
||||
},
|
||||
{
|
||||
name: "populated - more than one word",
|
||||
bitset: []int{255}, // [1, 0...]
|
||||
expected: 1,
|
||||
},
|
||||
{
|
||||
name: "populated - all ones",
|
||||
bitset: []int{5, 4, 3, 2, 1, 0}, // [1, 1, 1, 1, 1, 1]
|
||||
expected: 6,
|
||||
},
|
||||
{
|
||||
name: "populated - trailing zeroes",
|
||||
bitset: []int{5, 4, 3}, // [1, 1, 1, 0, 0, 0]
|
||||
expected: 3,
|
||||
},
|
||||
{
|
||||
name: "populated - interwoven 1",
|
||||
bitset: []int{4, 2, 0}, // [1, 0, 1, 0, 1]
|
||||
expected: 3,
|
||||
},
|
||||
{
|
||||
name: "populated - interwoven 2",
|
||||
bitset: []int{3, 1}, // [1, 0, 1, 0]
|
||||
expected: 2,
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
require := require.New(t)
|
||||
b := NewBits()
|
||||
|
||||
for _, bit := range test.bitset {
|
||||
b.Add(bit)
|
||||
}
|
||||
|
||||
require.Equal(test.expected, b.Len())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Bits_Bytes(t *testing.T) {
|
||||
type test struct {
|
||||
name string
|
||||
elts []int
|
||||
}
|
||||
|
||||
tests := []test{
|
||||
{
|
||||
name: "empty",
|
||||
elts: []int{},
|
||||
},
|
||||
{
|
||||
name: "single; element > 63",
|
||||
elts: []int{1337},
|
||||
},
|
||||
{
|
||||
name: "multiple",
|
||||
elts: []int{1, 2, 3},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
b := NewBits(tt.elts...)
|
||||
bytes := b.Bytes()
|
||||
fromBytes := BitsFromBytes(bytes)
|
||||
|
||||
require.Equal(len(tt.elts), fromBytes.Len())
|
||||
for _, elt := range tt.elts {
|
||||
require.True(fromBytes.Contains(elt))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
// Copyright (C) 2019-2024, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package set
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"slices"
|
||||
|
||||
"github.com/luxfi/node/utils"
|
||||
"github.com/luxfi/node/utils/sampler"
|
||||
"github.com/luxfi/node/utils/wrappers"
|
||||
|
||||
avajson "github.com/luxfi/node/utils/json"
|
||||
)
|
||||
|
||||
var _ json.Marshaler = (*Set[int])(nil)
|
||||
|
||||
// SampleableSet is a set of elements that supports sampling.
|
||||
type SampleableSet[T comparable] struct {
|
||||
// indices maps the element in the set to the index that it appears in
|
||||
// elements.
|
||||
indices map[T]int
|
||||
elements []T
|
||||
}
|
||||
|
||||
// OfSampleable returns a Set initialized with [elts]
|
||||
func OfSampleable[T comparable](elts ...T) SampleableSet[T] {
|
||||
s := NewSampleableSet[T](len(elts))
|
||||
s.Add(elts...)
|
||||
return s
|
||||
}
|
||||
|
||||
// Return a new sampleable set with initial capacity [size].
|
||||
// More or less than [size] elements can be added to this set.
|
||||
// Using NewSampleableSet() rather than SampleableSet[T]{} is just an
|
||||
// optimization that can be used if you know how many elements will be put in
|
||||
// this set.
|
||||
func NewSampleableSet[T comparable](size int) SampleableSet[T] {
|
||||
if size < 0 {
|
||||
return SampleableSet[T]{}
|
||||
}
|
||||
return SampleableSet[T]{
|
||||
indices: make(map[T]int, size),
|
||||
elements: make([]T, 0, size),
|
||||
}
|
||||
}
|
||||
|
||||
// Add all the elements to this set.
|
||||
// If the element is already in the set, nothing happens.
|
||||
func (s *SampleableSet[T]) Add(elements ...T) {
|
||||
s.resize(2 * len(elements))
|
||||
for _, e := range elements {
|
||||
s.add(e)
|
||||
}
|
||||
}
|
||||
|
||||
// Union adds all the elements from the provided set to this set.
|
||||
func (s *SampleableSet[T]) Union(set SampleableSet[T]) {
|
||||
s.resize(2 * set.Len())
|
||||
for _, e := range set.elements {
|
||||
s.add(e)
|
||||
}
|
||||
}
|
||||
|
||||
// Difference removes all the elements in [set] from [s].
|
||||
func (s *SampleableSet[T]) Difference(set SampleableSet[T]) {
|
||||
for _, e := range set.elements {
|
||||
s.remove(e)
|
||||
}
|
||||
}
|
||||
|
||||
// Contains returns true iff the set contains this element.
|
||||
func (s SampleableSet[T]) Contains(e T) bool {
|
||||
_, contains := s.indices[e]
|
||||
return contains
|
||||
}
|
||||
|
||||
// Overlaps returns true if the intersection of the set is non-empty
|
||||
func (s SampleableSet[T]) Overlaps(big SampleableSet[T]) bool {
|
||||
small := s
|
||||
if small.Len() > big.Len() {
|
||||
small, big = big, small
|
||||
}
|
||||
|
||||
for _, e := range small.elements {
|
||||
if _, ok := big.indices[e]; ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Len returns the number of elements in this set.
|
||||
func (s SampleableSet[_]) Len() int {
|
||||
return len(s.elements)
|
||||
}
|
||||
|
||||
// Remove all the given elements from this set.
|
||||
// If an element isn't in the set, it's ignored.
|
||||
func (s *SampleableSet[T]) Remove(elements ...T) {
|
||||
for _, e := range elements {
|
||||
s.remove(e)
|
||||
}
|
||||
}
|
||||
|
||||
// Clear empties this set
|
||||
func (s *SampleableSet[T]) Clear() {
|
||||
clear(s.indices)
|
||||
for i := range s.elements {
|
||||
s.elements[i] = utils.Zero[T]()
|
||||
}
|
||||
s.elements = s.elements[:0]
|
||||
}
|
||||
|
||||
// List converts this set into a list
|
||||
func (s SampleableSet[T]) List() []T {
|
||||
return slices.Clone(s.elements)
|
||||
}
|
||||
|
||||
// Equals returns true if the sets contain the same elements
|
||||
func (s SampleableSet[T]) Equals(other SampleableSet[T]) bool {
|
||||
if len(s.indices) != len(other.indices) {
|
||||
return false
|
||||
}
|
||||
for k := range s.indices {
|
||||
if _, ok := other.indices[k]; !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (s SampleableSet[T]) Sample(numToSample int) []T {
|
||||
if numToSample <= 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
uniform := sampler.NewUniform()
|
||||
uniform.Initialize(uint64(len(s.elements)))
|
||||
indices, _ := uniform.Sample(min(len(s.elements), numToSample))
|
||||
elements := make([]T, len(indices))
|
||||
for i, index := range indices {
|
||||
elements[i] = s.elements[index]
|
||||
}
|
||||
return elements
|
||||
}
|
||||
|
||||
func (s *SampleableSet[T]) UnmarshalJSON(b []byte) error {
|
||||
str := string(b)
|
||||
if str == avajson.Null {
|
||||
return nil
|
||||
}
|
||||
var elements []T
|
||||
if err := json.Unmarshal(b, &elements); err != nil {
|
||||
return err
|
||||
}
|
||||
s.Clear()
|
||||
s.Add(elements...)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SampleableSet[_]) MarshalJSON() ([]byte, error) {
|
||||
var (
|
||||
elementBytes = make([][]byte, len(s.elements))
|
||||
err error
|
||||
)
|
||||
for i, e := range s.elements {
|
||||
elementBytes[i], err = json.Marshal(e)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
// Sort for determinism
|
||||
slices.SortFunc(elementBytes, bytes.Compare)
|
||||
|
||||
// Build the JSON
|
||||
var (
|
||||
jsonBuf = bytes.Buffer{}
|
||||
errs = wrappers.Errs{}
|
||||
)
|
||||
_, err = jsonBuf.WriteString("[")
|
||||
errs.Add(err)
|
||||
for i, elt := range elementBytes {
|
||||
_, err := jsonBuf.Write(elt)
|
||||
errs.Add(err)
|
||||
if i != len(elementBytes)-1 {
|
||||
_, err := jsonBuf.WriteString(",")
|
||||
errs.Add(err)
|
||||
}
|
||||
}
|
||||
_, err = jsonBuf.WriteString("]")
|
||||
errs.Add(err)
|
||||
|
||||
return jsonBuf.Bytes(), errs.Err
|
||||
}
|
||||
|
||||
func (s *SampleableSet[T]) resize(size int) {
|
||||
if s.elements == nil {
|
||||
if minSetSize > size {
|
||||
size = minSetSize
|
||||
}
|
||||
s.indices = make(map[T]int, size)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SampleableSet[T]) add(e T) {
|
||||
_, ok := s.indices[e]
|
||||
if ok {
|
||||
return
|
||||
}
|
||||
|
||||
s.indices[e] = len(s.elements)
|
||||
s.elements = append(s.elements, e)
|
||||
}
|
||||
|
||||
func (s *SampleableSet[T]) remove(e T) {
|
||||
indexToRemove, ok := s.indices[e]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
lastIndex := len(s.elements) - 1
|
||||
if indexToRemove != lastIndex {
|
||||
lastElement := s.elements[lastIndex]
|
||||
|
||||
s.indices[lastElement] = indexToRemove
|
||||
s.elements[indexToRemove] = lastElement
|
||||
}
|
||||
|
||||
delete(s.indices, e)
|
||||
s.elements[lastIndex] = utils.Zero[T]()
|
||||
s.elements = s.elements[:lastIndex]
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
// Copyright (C) 2019-2024, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package set
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestSampleableSet(t *testing.T) {
|
||||
require := require.New(t)
|
||||
id1 := 1
|
||||
|
||||
s := SampleableSet[int]{}
|
||||
|
||||
s.Add(id1)
|
||||
require.True(s.Contains(id1))
|
||||
|
||||
s.Remove(id1)
|
||||
require.False(s.Contains(id1))
|
||||
|
||||
s.Add(id1)
|
||||
require.True(s.Contains(id1))
|
||||
require.Len(s.List(), 1)
|
||||
require.Equal(id1, s.List()[0])
|
||||
|
||||
s.Clear()
|
||||
require.False(s.Contains(id1))
|
||||
|
||||
s.Add(id1)
|
||||
|
||||
s2 := SampleableSet[int]{}
|
||||
|
||||
require.False(s.Overlaps(s2))
|
||||
|
||||
s2.Union(s)
|
||||
require.True(s2.Contains(id1))
|
||||
require.True(s.Overlaps(s2))
|
||||
|
||||
s2.Difference(s)
|
||||
require.False(s2.Contains(id1))
|
||||
require.False(s.Overlaps(s2))
|
||||
}
|
||||
|
||||
func TestSampleableSetClear(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
set := SampleableSet[int]{}
|
||||
for i := 0; i < 25; i++ {
|
||||
set.Add(i)
|
||||
}
|
||||
set.Clear()
|
||||
require.Zero(set.Len())
|
||||
set.Add(1337)
|
||||
require.Equal(1, set.Len())
|
||||
}
|
||||
|
||||
func TestSampleableSetMarshalJSON(t *testing.T) {
|
||||
require := require.New(t)
|
||||
set := SampleableSet[int]{}
|
||||
{
|
||||
asJSON, err := set.MarshalJSON()
|
||||
require.NoError(err)
|
||||
require.Equal("[]", string(asJSON))
|
||||
}
|
||||
id1, id2 := 1, 2
|
||||
id1JSON, err := json.Marshal(id1)
|
||||
require.NoError(err)
|
||||
id2JSON, err := json.Marshal(id2)
|
||||
require.NoError(err)
|
||||
set.Add(id1)
|
||||
{
|
||||
asJSON, err := set.MarshalJSON()
|
||||
require.NoError(err)
|
||||
require.Equal(fmt.Sprintf("[%s]", string(id1JSON)), string(asJSON))
|
||||
}
|
||||
set.Add(id2)
|
||||
{
|
||||
asJSON, err := set.MarshalJSON()
|
||||
require.NoError(err)
|
||||
require.Equal(fmt.Sprintf("[%s,%s]", string(id1JSON), string(id2JSON)), string(asJSON))
|
||||
}
|
||||
}
|
||||
|
||||
func TestSampleableSetUnmarshalJSON(t *testing.T) {
|
||||
require := require.New(t)
|
||||
set := SampleableSet[int]{}
|
||||
{
|
||||
require.NoError(set.UnmarshalJSON([]byte("[]")))
|
||||
require.Zero(set.Len())
|
||||
}
|
||||
id1, id2 := 1, 2
|
||||
id1JSON, err := json.Marshal(id1)
|
||||
require.NoError(err)
|
||||
id2JSON, err := json.Marshal(id2)
|
||||
require.NoError(err)
|
||||
{
|
||||
require.NoError(set.UnmarshalJSON([]byte(fmt.Sprintf("[%s]", string(id1JSON)))))
|
||||
require.Equal(1, set.Len())
|
||||
require.True(set.Contains(id1))
|
||||
}
|
||||
{
|
||||
require.NoError(set.UnmarshalJSON([]byte(fmt.Sprintf("[%s,%s]", string(id1JSON), string(id2JSON)))))
|
||||
require.Equal(2, set.Len())
|
||||
require.True(set.Contains(id1))
|
||||
require.True(set.Contains(id2))
|
||||
}
|
||||
{
|
||||
require.NoError(set.UnmarshalJSON([]byte(fmt.Sprintf("[%d,%d,%d]", 3, 4, 5))))
|
||||
require.Equal(3, set.Len())
|
||||
require.True(set.Contains(3))
|
||||
require.True(set.Contains(4))
|
||||
require.True(set.Contains(5))
|
||||
}
|
||||
{
|
||||
require.NoError(set.UnmarshalJSON([]byte(fmt.Sprintf("[%d,%d,%d, %d]", 3, 4, 5, 3))))
|
||||
require.Equal(3, set.Len())
|
||||
require.True(set.Contains(3))
|
||||
require.True(set.Contains(4))
|
||||
require.True(set.Contains(5))
|
||||
}
|
||||
{
|
||||
set1 := SampleableSet[int]{}
|
||||
set2 := SampleableSet[int]{}
|
||||
require.NoError(set1.UnmarshalJSON([]byte(fmt.Sprintf("[%s,%s]", string(id1JSON), string(id2JSON)))))
|
||||
require.NoError(set2.UnmarshalJSON([]byte(fmt.Sprintf("[%s,%s]", string(id2JSON), string(id1JSON)))))
|
||||
require.True(set1.Equals(set2))
|
||||
}
|
||||
}
|
||||
|
||||
func TestOfSampleable(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
elements []int
|
||||
expected []int
|
||||
}{
|
||||
{
|
||||
name: "nil",
|
||||
elements: nil,
|
||||
expected: []int{},
|
||||
},
|
||||
{
|
||||
name: "empty",
|
||||
elements: []int{},
|
||||
expected: []int{},
|
||||
},
|
||||
{
|
||||
name: "unique elements",
|
||||
elements: []int{1, 2, 3},
|
||||
expected: []int{1, 2, 3},
|
||||
},
|
||||
{
|
||||
name: "duplicate elements",
|
||||
elements: []int{1, 2, 3, 1, 2, 3},
|
||||
expected: []int{1, 2, 3},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
s := OfSampleable(tt.elements...)
|
||||
|
||||
require.Equal(len(tt.expected), s.Len())
|
||||
for _, expected := range tt.expected {
|
||||
require.True(s.Contains(expected))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
+194
@@ -0,0 +1,194 @@
|
||||
// Copyright (C) 2019-2024, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package set
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"slices"
|
||||
|
||||
"golang.org/x/exp/maps"
|
||||
|
||||
"github.com/luxfi/node/utils"
|
||||
"github.com/luxfi/node/utils/wrappers"
|
||||
|
||||
avajson "github.com/luxfi/node/utils/json"
|
||||
)
|
||||
|
||||
// The minimum capacity of a set
|
||||
const minSetSize = 16
|
||||
|
||||
var _ json.Marshaler = (*Set[int])(nil)
|
||||
|
||||
// Set is a set of elements.
|
||||
type Set[T comparable] map[T]struct{}
|
||||
|
||||
// Of returns a Set initialized with [elts]
|
||||
func Of[T comparable](elts ...T) Set[T] {
|
||||
s := NewSet[T](len(elts))
|
||||
s.Add(elts...)
|
||||
return s
|
||||
}
|
||||
|
||||
// Return a new set with initial capacity [size].
|
||||
// More or less than [size] elements can be added to this set.
|
||||
// Using NewSet() rather than Set[T]{} is just an optimization that can
|
||||
// be used if you know how many elements will be put in this set.
|
||||
func NewSet[T comparable](size int) Set[T] {
|
||||
if size < 0 {
|
||||
return Set[T]{}
|
||||
}
|
||||
return make(map[T]struct{}, size)
|
||||
}
|
||||
|
||||
func (s *Set[T]) resize(size int) {
|
||||
if *s == nil {
|
||||
if minSetSize > size {
|
||||
size = minSetSize
|
||||
}
|
||||
*s = make(map[T]struct{}, size)
|
||||
}
|
||||
}
|
||||
|
||||
// Add all the elements to this set.
|
||||
// If the element is already in the set, nothing happens.
|
||||
func (s *Set[T]) Add(elts ...T) {
|
||||
s.resize(2 * len(elts))
|
||||
for _, elt := range elts {
|
||||
(*s)[elt] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
// Union adds all the elements from the provided set to this set.
|
||||
func (s *Set[T]) Union(set Set[T]) {
|
||||
s.resize(2 * set.Len())
|
||||
for elt := range set {
|
||||
(*s)[elt] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
// Difference removes all the elements in [set] from [s].
|
||||
func (s *Set[T]) Difference(set Set[T]) {
|
||||
for elt := range set {
|
||||
delete(*s, elt)
|
||||
}
|
||||
}
|
||||
|
||||
// Contains returns true iff the set contains this element.
|
||||
func (s *Set[T]) Contains(elt T) bool {
|
||||
_, contains := (*s)[elt]
|
||||
return contains
|
||||
}
|
||||
|
||||
// Overlaps returns true if the intersection of the set is non-empty
|
||||
func (s *Set[T]) Overlaps(big Set[T]) bool {
|
||||
small := *s
|
||||
if small.Len() > big.Len() {
|
||||
small, big = big, small
|
||||
}
|
||||
|
||||
for elt := range small {
|
||||
if _, ok := big[elt]; ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Len returns the number of elements in this set.
|
||||
func (s Set[_]) Len() int {
|
||||
return len(s)
|
||||
}
|
||||
|
||||
// Remove all the given elements from this set.
|
||||
// If an element isn't in the set, it's ignored.
|
||||
func (s *Set[T]) Remove(elts ...T) {
|
||||
for _, elt := range elts {
|
||||
delete(*s, elt)
|
||||
}
|
||||
}
|
||||
|
||||
// Clear empties this set
|
||||
func (s *Set[_]) Clear() {
|
||||
clear(*s)
|
||||
}
|
||||
|
||||
// List converts this set into a list
|
||||
func (s Set[T]) List() []T {
|
||||
return maps.Keys(s)
|
||||
}
|
||||
|
||||
// Equals returns true if the sets contain the same elements
|
||||
func (s Set[T]) Equals(other Set[T]) bool {
|
||||
return maps.Equal(s, other)
|
||||
}
|
||||
|
||||
// Removes and returns an element.
|
||||
// If the set is empty, does nothing and returns false.
|
||||
func (s *Set[T]) Pop() (T, bool) {
|
||||
for elt := range *s {
|
||||
delete(*s, elt)
|
||||
return elt, true
|
||||
}
|
||||
return utils.Zero[T](), false
|
||||
}
|
||||
|
||||
func (s *Set[T]) UnmarshalJSON(b []byte) error {
|
||||
str := string(b)
|
||||
if str == avajson.Null {
|
||||
return nil
|
||||
}
|
||||
var elts []T
|
||||
if err := json.Unmarshal(b, &elts); err != nil {
|
||||
return err
|
||||
}
|
||||
s.Clear()
|
||||
s.Add(elts...)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s Set[_]) MarshalJSON() ([]byte, error) {
|
||||
var (
|
||||
eltBytes = make([][]byte, len(s))
|
||||
i int
|
||||
err error
|
||||
)
|
||||
for elt := range s {
|
||||
eltBytes[i], err = json.Marshal(elt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
i++
|
||||
}
|
||||
// Sort for determinism
|
||||
slices.SortFunc(eltBytes, bytes.Compare)
|
||||
|
||||
// Build the JSON
|
||||
var (
|
||||
jsonBuf = bytes.Buffer{}
|
||||
errs = wrappers.Errs{}
|
||||
)
|
||||
_, err = jsonBuf.WriteString("[")
|
||||
errs.Add(err)
|
||||
for i, elt := range eltBytes {
|
||||
_, err := jsonBuf.Write(elt)
|
||||
errs.Add(err)
|
||||
if i != len(eltBytes)-1 {
|
||||
_, err := jsonBuf.WriteString(",")
|
||||
errs.Add(err)
|
||||
}
|
||||
}
|
||||
_, err = jsonBuf.WriteString("]")
|
||||
errs.Add(err)
|
||||
|
||||
return jsonBuf.Bytes(), errs.Err
|
||||
}
|
||||
|
||||
// Returns a random element. If the set is empty, returns false
|
||||
func (s *Set[T]) Peek() (T, bool) {
|
||||
for elt := range *s {
|
||||
return elt, true
|
||||
}
|
||||
return utils.Zero[T](), false
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
// Copyright (C) 2019-2024, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package set
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func BenchmarkSetList(b *testing.B) {
|
||||
sizes := []int{5, 25, 100, 100_000} // Test with various sizes
|
||||
for size := range sizes {
|
||||
b.Run(strconv.Itoa(size), func(b *testing.B) {
|
||||
set := Set[int]{}
|
||||
for i := 0; i < size; i++ {
|
||||
set.Add(i)
|
||||
}
|
||||
b.ResetTimer()
|
||||
for n := 0; n < b.N; n++ {
|
||||
set.List()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSetClear(b *testing.B) {
|
||||
for _, numElts := range []int{10, 25, 50, 100, 250, 500, 1000} {
|
||||
b.Run(strconv.Itoa(numElts), func(b *testing.B) {
|
||||
set := NewSet[int](numElts)
|
||||
for n := 0; n < b.N; n++ {
|
||||
for i := 0; i < numElts; i++ {
|
||||
set.Add(i)
|
||||
}
|
||||
set.Clear()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
+228
@@ -0,0 +1,228 @@
|
||||
// Copyright (C) 2019-2024, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package set
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestSet(t *testing.T) {
|
||||
require := require.New(t)
|
||||
id1 := 1
|
||||
|
||||
s := Set[int]{id1: struct{}{}}
|
||||
|
||||
s.Add(id1)
|
||||
require.True(s.Contains(id1))
|
||||
|
||||
s.Remove(id1)
|
||||
require.False(s.Contains(id1))
|
||||
|
||||
s.Add(id1)
|
||||
require.True(s.Contains(id1))
|
||||
require.Len(s.List(), 1)
|
||||
require.Equal(id1, s.List()[0])
|
||||
|
||||
s.Clear()
|
||||
require.False(s.Contains(id1))
|
||||
|
||||
s.Add(id1)
|
||||
|
||||
s2 := Set[int]{}
|
||||
|
||||
require.False(s.Overlaps(s2))
|
||||
|
||||
s2.Union(s)
|
||||
require.True(s2.Contains(id1))
|
||||
require.True(s.Overlaps(s2))
|
||||
|
||||
s2.Difference(s)
|
||||
require.False(s2.Contains(id1))
|
||||
require.False(s.Overlaps(s2))
|
||||
}
|
||||
|
||||
func TestOf(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
elements []int
|
||||
expected []int
|
||||
}{
|
||||
{
|
||||
name: "nil",
|
||||
elements: nil,
|
||||
expected: []int{},
|
||||
},
|
||||
{
|
||||
name: "empty",
|
||||
elements: []int{},
|
||||
expected: []int{},
|
||||
},
|
||||
{
|
||||
name: "unique elements",
|
||||
elements: []int{1, 2, 3},
|
||||
expected: []int{1, 2, 3},
|
||||
},
|
||||
{
|
||||
name: "duplicate elements",
|
||||
elements: []int{1, 2, 3, 1, 2, 3},
|
||||
expected: []int{1, 2, 3},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
s := Of(tt.elements...)
|
||||
|
||||
require.Len(s, len(tt.expected))
|
||||
for _, expected := range tt.expected {
|
||||
require.True(s.Contains(expected))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetClear(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
set := Set[int]{}
|
||||
for i := 0; i < 25; i++ {
|
||||
set.Add(i)
|
||||
}
|
||||
set.Clear()
|
||||
require.Empty(set)
|
||||
set.Add(1337)
|
||||
require.Len(set, 1)
|
||||
}
|
||||
|
||||
func TestSetPop(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
var s Set[int]
|
||||
_, ok := s.Pop()
|
||||
require.False(ok)
|
||||
|
||||
s = make(Set[int])
|
||||
_, ok = s.Pop()
|
||||
require.False(ok)
|
||||
|
||||
id1, id2 := 0, 1
|
||||
s.Add(id1, id2)
|
||||
|
||||
got, ok := s.Pop()
|
||||
require.True(ok)
|
||||
require.True(got == id1 || got == id2)
|
||||
require.Equal(1, s.Len())
|
||||
|
||||
got, ok = s.Pop()
|
||||
require.True(ok)
|
||||
require.True(got == id1 || got == id2)
|
||||
require.Zero(s.Len())
|
||||
|
||||
_, ok = s.Pop()
|
||||
require.False(ok)
|
||||
}
|
||||
|
||||
func TestSetMarshalJSON(t *testing.T) {
|
||||
require := require.New(t)
|
||||
set := Set[int]{}
|
||||
{
|
||||
asJSON, err := set.MarshalJSON()
|
||||
require.NoError(err)
|
||||
require.Equal("[]", string(asJSON))
|
||||
}
|
||||
id1, id2 := 1, 2
|
||||
id1JSON, err := json.Marshal(id1)
|
||||
require.NoError(err)
|
||||
id2JSON, err := json.Marshal(id2)
|
||||
require.NoError(err)
|
||||
set.Add(id1)
|
||||
{
|
||||
asJSON, err := set.MarshalJSON()
|
||||
require.NoError(err)
|
||||
require.Equal(fmt.Sprintf("[%s]", string(id1JSON)), string(asJSON))
|
||||
}
|
||||
set.Add(id2)
|
||||
{
|
||||
asJSON, err := set.MarshalJSON()
|
||||
require.NoError(err)
|
||||
require.Equal(fmt.Sprintf("[%s,%s]", string(id1JSON), string(id2JSON)), string(asJSON))
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetUnmarshalJSON(t *testing.T) {
|
||||
require := require.New(t)
|
||||
set := Set[int]{}
|
||||
{
|
||||
require.NoError(set.UnmarshalJSON([]byte("[]")))
|
||||
require.Empty(set)
|
||||
}
|
||||
id1, id2 := 1, 2
|
||||
id1JSON, err := json.Marshal(id1)
|
||||
require.NoError(err)
|
||||
id2JSON, err := json.Marshal(id2)
|
||||
require.NoError(err)
|
||||
{
|
||||
require.NoError(set.UnmarshalJSON([]byte(fmt.Sprintf("[%s]", string(id1JSON)))))
|
||||
require.Len(set, 1)
|
||||
require.Contains(set, id1)
|
||||
}
|
||||
{
|
||||
require.NoError(set.UnmarshalJSON([]byte(fmt.Sprintf("[%s,%s]", string(id1JSON), string(id2JSON)))))
|
||||
require.Len(set, 2)
|
||||
require.Contains(set, id1)
|
||||
require.Contains(set, id2)
|
||||
}
|
||||
{
|
||||
require.NoError(set.UnmarshalJSON([]byte(fmt.Sprintf("[%d,%d,%d]", 3, 4, 5))))
|
||||
require.Len(set, 3)
|
||||
require.Contains(set, 3)
|
||||
require.Contains(set, 4)
|
||||
require.Contains(set, 5)
|
||||
}
|
||||
{
|
||||
require.NoError(set.UnmarshalJSON([]byte(fmt.Sprintf("[%d,%d,%d, %d]", 3, 4, 5, 3))))
|
||||
require.Len(set, 3)
|
||||
require.Contains(set, 3)
|
||||
require.Contains(set, 4)
|
||||
require.Contains(set, 5)
|
||||
}
|
||||
{
|
||||
set1 := Set[int]{}
|
||||
set2 := Set[int]{}
|
||||
require.NoError(set1.UnmarshalJSON([]byte(fmt.Sprintf("[%s,%s]", string(id1JSON), string(id2JSON)))))
|
||||
require.NoError(set2.UnmarshalJSON([]byte(fmt.Sprintf("[%s,%s]", string(id2JSON), string(id1JSON)))))
|
||||
require.Equal(set1, set2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetReflectJSONMarshal(t *testing.T) {
|
||||
require := require.New(t)
|
||||
set := Set[int]{}
|
||||
{
|
||||
asJSON, err := json.Marshal(set)
|
||||
require.NoError(err)
|
||||
require.Equal("[]", string(asJSON))
|
||||
}
|
||||
id1JSON, err := json.Marshal(1)
|
||||
require.NoError(err)
|
||||
id2JSON, err := json.Marshal(2)
|
||||
require.NoError(err)
|
||||
set.Add(1)
|
||||
{
|
||||
asJSON, err := json.Marshal(set)
|
||||
require.NoError(err)
|
||||
require.Equal(fmt.Sprintf("[%s]", string(id1JSON)), string(asJSON))
|
||||
}
|
||||
set.Add(2)
|
||||
{
|
||||
asJSON, err := json.Marshal(set)
|
||||
require.NoError(err)
|
||||
require.Equal(fmt.Sprintf("[%s,%s]", string(id1JSON), string(id2JSON)), string(asJSON))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user