Skip to content

Commit 36dea43

Browse files
authored
Add generic support; CopyFrom() and Init() methods (#20)
This code was last updated before Go generics. It's an obvious win here, and was easy to do. Also adds CopyFrom() and Init() method in support of lightstep/otel-launcher-go#576 and new tests.
1 parent ab68206 commit 36dea43

17 files changed

+254
-121
lines changed

Diff for: .circleci/config.yml

-16
This file was deleted.

Diff for: .github/ISSUE_TEMPLATE/bug_report.md

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
name: Bug report
3+
about: Create a report to help us improve
4+
title: ''
5+
labels: ''
6+
assignees: ''
7+
8+
---
9+
10+
**Describe the bug**
11+
A clear and concise description of what the bug is.
12+
13+
**To Reproduce**
14+
Steps to reproduce the behavior. Include code samples here when possible.
15+
16+
**Expected behavior**
17+
A clear and concise description of what you expected to happen.
18+
19+
**Additional context**
20+
Add any other context about the problem here.

Diff for: .github/ISSUE_TEMPLATE/feature_request.md

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
name: Feature request
3+
about: Suggest an idea for this project
4+
title: ''
5+
labels: ''
6+
assignees: ''
7+
8+
---
9+
10+
**Is your feature request related to a problem? Please describe.**
11+
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12+
13+
**Describe the solution you'd like**
14+
A clear and concise description of what you want to happen.
15+
16+
**Describe alternatives you've considered**
17+
A clear and concise description of any alternative solutions or features you've considered.
18+
19+
**Additional context**
20+
Add any other context or screenshots about the feature request here.

Diff for: .github/pull_request_template.md

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
**Description:** <Describe what has changed.
2+
Ex. Fixing a bug - Describe the bug and how this fixes the issue.
3+
Ex. Adding a feature - Explain what this achieves.>
4+
5+
**Link to tracking Issue:** <Issue number if applicable>
6+
7+
**Testing:** < Describe what testing was performed and which tests were added.>
8+
9+
**Documentation:** < Describe the documentation added.>

Diff for: .github/workflows/build.yml

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
name: build
2+
3+
on:
4+
push:
5+
branches: [ main ]
6+
pull_request:
7+
branches: [ main ]
8+
9+
jobs:
10+
11+
build:
12+
strategy:
13+
matrix:
14+
go-version: [1.21.3]
15+
name: Build
16+
runs-on: ubuntu-latest
17+
steps:
18+
19+
- name: Set up Go 1.x
20+
uses: actions/setup-go@v2
21+
with:
22+
go-version: ${{ matrix.go-version }}
23+
id: go
24+
25+
- name: Check out code into the Go module directory
26+
uses: actions/checkout@v2
27+
28+
- name: Build
29+
run: go test -v ./...
30+
31+
- name: Code coverage
32+
run: bash <(curl -s https://codecov.io/bash)

Diff for: README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,4 @@ VarOpt supports merging independently collected samples one
5555
observation at a time. This is useful for building distributed
5656
sampling schemes. In this use-case, each node in a distributed system
5757
computes a weighted sample. To combine samples, simply input all the
58-
observations and their corresponding weights into a new VarOpt sample.
58+
observations and their corresponding weights into a new VarOpt sample.

Diff for: benchmark_test.go

+4-2
Original file line numberDiff line numberDiff line change
@@ -56,17 +56,19 @@ func BenchmarkAdd_Exp_1000000(b *testing.B) {
5656
benchmarkAdd(b, 1000000, expValue)
5757
}
5858

59+
type thing struct{}
60+
5961
func benchmarkAdd(b *testing.B, size int, f func(rnd *rand.Rand) float64) {
6062
b.ReportAllocs()
6163
rnd := rand.New(rand.NewSource(3331))
62-
v := varopt.New(size, rnd)
64+
v := varopt.New[thing](size, rnd)
6365
weights := make([]float64, b.N)
6466
for i := 0; i < b.N; i++ {
6567
weights[i] = f(rnd)
6668
}
6769

6870
b.StartTimer()
6971
for i := 0; i < b.N; i++ {
70-
v.Add(nil, weights[i])
72+
v.Add(thing{}, weights[i])
7173
}
7274
}

Diff for: frequency_test.go

+5-6
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ type curve struct {
1616
stddev float64
1717
}
1818

19-
type point struct {
19+
type testPoint struct {
2020
color int
2121
xvalue float64
2222
}
@@ -47,7 +47,7 @@ func ExampleVaropt_GetOriginalWeight() {
4747

4848
// Construct a timeseries consisting of three colored signals,
4949
// for x=0 to x=60 seconds.
50-
var points []point
50+
var points []testPoint
5151

5252
// origCounts stores the original signals at second granularity.
5353
origCounts := make([][]int, len(colors))
@@ -66,7 +66,7 @@ func ExampleVaropt_GetOriginalWeight() {
6666
continue
6767
}
6868
origCounts[choose][int(math.Floor(xvalue))]++
69-
points = append(points, point{
69+
points = append(points, testPoint{
7070
color: choose,
7171
xvalue: xvalue,
7272
})
@@ -83,7 +83,7 @@ func ExampleVaropt_GetOriginalWeight() {
8383
// weight. This ensures a uniform distribution of points in each
8484
// second.
8585
sampleSize := int(sampleRatio * float64(totalCount))
86-
sampler := varopt.New(sampleSize, rnd)
86+
sampler := varopt.New[testPoint](sampleSize, rnd)
8787
for _, point := range points {
8888
second := int(math.Floor(point.xvalue))
8989
prob := float64(xcount[second]) / float64(totalCount)
@@ -103,9 +103,8 @@ func ExampleVaropt_GetOriginalWeight() {
103103
// The effective count of each sample point is its output
104104
// weight divided by its original weight.
105105
for i := 0; i < sampler.Size(); i++ {
106-
sample, weight := sampler.Get(i)
106+
point, weight := sampler.Get(i)
107107
origWeight := sampler.GetOriginalWeight(i)
108-
point := sample.(point)
109108
second := int(math.Floor(point.xvalue))
110109
sampleCounts[point.color][second] += (weight / origWeight)
111110
pointCounts[second]++

Diff for: go.mod

+11-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
module github.com/lightstep/varopt
22

3-
go 1.15
3+
go 1.21
44

5-
require github.com/stretchr/testify v1.4.0
5+
require github.com/stretchr/testify v1.8.4
6+
7+
require (
8+
github.com/davecgh/go-spew v1.1.1 // indirect
9+
github.com/kr/pretty v0.3.1 // indirect
10+
github.com/pmezard/go-difflib v1.0.0 // indirect
11+
github.com/rogpeppe/go-internal v1.11.0 // indirect
12+
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
13+
gopkg.in/yaml.v3 v3.0.1 // indirect
14+
)

Diff for: go.sum

+20-8
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,23 @@
1-
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
2-
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
1+
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
2+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
3+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
4+
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
5+
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
6+
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
7+
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
8+
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
9+
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
10+
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
11+
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
312
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
413
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
5-
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
6-
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
7-
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
8-
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
14+
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
15+
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
16+
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
17+
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
18+
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
919
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
10-
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
11-
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
20+
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
21+
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
22+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
23+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

Diff for: internal/sampleheap.go

+5-5
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@
22

33
package internal
44

5-
type Vsample struct {
6-
Sample interface{}
5+
type Vsample[T any] struct {
6+
Sample T
77
Weight float64
88
}
99

10-
type SampleHeap []Vsample
10+
type SampleHeap[T any] []Vsample[T]
1111

12-
func (sh *SampleHeap) Push(v Vsample) {
12+
func (sh *SampleHeap[T]) Push(v Vsample[T]) {
1313
l := append(*sh, v)
1414
n := len(l) - 1
1515

@@ -27,7 +27,7 @@ func (sh *SampleHeap) Push(v Vsample) {
2727
*sh = l
2828
}
2929

30-
func (sh *SampleHeap) Pop() Vsample {
30+
func (sh *SampleHeap[T]) Pop() Vsample[T] {
3131
l := *sh
3232
n := len(l) - 1
3333
result := l[0]

Diff for: internal/sampleheap_test.go

+6-3
Original file line numberDiff line numberDiff line change
@@ -38,17 +38,20 @@ func (s *simpleHeap) Pop() interface{} {
3838
}
3939

4040
func TestLargeHeap(t *testing.T) {
41-
var L internal.SampleHeap
41+
var L internal.SampleHeap[float64]
4242
var S simpleHeap
4343

4444
for i := 0; i < 1e6; i++ {
4545
v := rand.NormFloat64()
46-
L.Push(internal.Vsample{Weight: v})
46+
L.Push(internal.Vsample[float64]{
47+
Sample: v,
48+
Weight: v,
49+
})
4750
heap.Push(&S, v)
4851
}
4952

5053
for len(S) > 0 {
51-
v1 := heap.Pop(&S).(float64)
54+
v1 := heap.Pop(&S)
5255
v2 := L.Pop().Weight
5356

5457
require.Equal(t, v1, v2)

Diff for: simple/simple.go

+17-16
Original file line numberDiff line numberDiff line change
@@ -4,62 +4,63 @@ package simple
44

55
import (
66
"math/rand"
7-
8-
"github.com/lightstep/varopt"
97
)
108

119
// Simple implements unweighted reservoir sampling using Algorithm R
1210
// from "Random sampling with a reservoir" by Jeffrey Vitter (1985)
1311
// https://en.wikipedia.org/wiki/Reservoir_sampling#Algorithm_R
14-
type Simple struct {
12+
type Simple[T any] struct {
1513
capacity int
1614
observed int
17-
buffer []varopt.Sample
15+
buffer []T
1816
rnd *rand.Rand
1917
}
2018

2119
// New returns a simple reservoir sampler with given capacity
2220
// (i.e., reservoir size) and random number generator.
23-
func New(capacity int, rnd *rand.Rand) *Simple {
24-
return &Simple{
21+
func New[T any](capacity int, rnd *rand.Rand) *Simple[T] {
22+
s := &Simple[T]{}
23+
s.Init(capacity, rnd)
24+
return s
25+
}
26+
27+
func (s *Simple[T]) Init(capacity int, rnd *rand.Rand) {
28+
*s = Simple[T]{
2529
capacity: capacity,
30+
buffer: make([]T, 0, s.capacity),
2631
rnd: rnd,
2732
}
2833
}
2934

3035
// Add considers a new observation for the sample. Items have unit
3136
// weight.
32-
func (s *Simple) Add(span varopt.Sample) {
37+
func (s *Simple[T]) Add(item T) {
3338
s.observed++
3439

35-
if s.buffer == nil {
36-
s.buffer = make([]varopt.Sample, 0, s.capacity)
37-
}
38-
3940
if len(s.buffer) < s.capacity {
40-
s.buffer = append(s.buffer, span)
41+
s.buffer = append(s.buffer, item)
4142
return
4243
}
4344

4445
// Give this a capacity/observed chance of replacing an existing entry.
4546
index := s.rnd.Intn(s.observed)
4647
if index < s.capacity {
47-
s.buffer[index] = span
48+
s.buffer[index] = item
4849
}
4950
}
5051

5152
// Get returns the i'th selected item from the sample.
52-
func (s *Simple) Get(i int) varopt.Sample {
53+
func (s *Simple[T]) Get(i int) T {
5354
return s.buffer[i]
5455
}
5556

5657
// Size returns the number of items in the sample. If the reservoir is
5758
// full, Size() equals Capacity().
58-
func (s *Simple) Size() int {
59+
func (s *Simple[T]) Size() int {
5960
return len(s.buffer)
6061
}
6162

6263
// Count returns the number of items that were observed.
63-
func (s *Simple) Count() int {
64+
func (s *Simple[T]) Count() int {
6465
return s.observed
6566
}

Diff for: simple/simple_test.go

+3-5
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@ import (
1010
"github.com/stretchr/testify/require"
1111
)
1212

13-
type iRec int
14-
1513
func TestSimple(t *testing.T) {
1614
const (
1715
popSize = 1e6
@@ -22,19 +20,19 @@ func TestSimple(t *testing.T) {
2220

2321
rnd := rand.New(rand.NewSource(17167))
2422

25-
ss := simple.New(sampleSize, rnd)
23+
ss := simple.New[int](sampleSize, rnd)
2624

2725
psum := 0.
2826
for i := 0; i < popSize; i++ {
29-
ss.Add(iRec(i))
27+
ss.Add(i)
3028
psum += float64(i)
3129
}
3230

3331
require.Equal(t, ss.Size(), sampleSize)
3432

3533
ssum := 0.0
3634
for i := 0; i < sampleSize; i++ {
37-
ssum += float64(ss.Get(i).(iRec))
35+
ssum += float64(ss.Get(i))
3836
}
3937

4038
require.InEpsilon(t, ssum/float64(ss.Size()), psum/popSize, epsilon)

0 commit comments

Comments
 (0)