diff --git a/benchmark123_test.go b/benchmark123_test.go new file mode 100644 index 00000000..effb89e5 --- /dev/null +++ b/benchmark123_test.go @@ -0,0 +1,164 @@ +//go:build go1.23 +// +build go1.23 + +package roaring + +import ( + "math/rand" + "testing" +) + +func BenchmarkIterator123(b *testing.B) { + bm := NewBitmap() + domain := 100000000 + count := 10000 + for j := 0; j < count; j++ { + v := uint32(rand.Intn(domain)) + bm.Add(v) + } + i := IntIterator{} + expectedCardinality := bm.GetCardinality() + counter := uint64(0) + + b.Run("simple iteration with alloc", func(b *testing.B) { + for n := 0; n < b.N; n++ { + counter = 0 + i := bm.Iterator() + for i.HasNext() { + i.Next() + counter++ + } + } + b.StopTimer() + }) + if counter != expectedCardinality { + b.Fatalf("Cardinalities don't match: %d, %d", counter, expectedCardinality) + } + b.Run("simple iteration", func(b *testing.B) { + for n := 0; n < b.N; n++ { + counter = 0 + i.Initialize(bm) + for i.HasNext() { + i.Next() + counter++ + } + } + b.StopTimer() + }) + if counter != expectedCardinality { + b.Fatalf("Cardinalities don't match: %d, %d", counter, expectedCardinality) + } + b.Run("values iteration", func(b *testing.B) { + for n := 0; n < b.N; n++ { + counter = 0 + Values(bm)(func(_ uint32) bool { + counter++ + return true + }) + } + b.StopTimer() + }) + if counter != expectedCardinality { + b.Fatalf("Cardinalities don't match: %d, %d", counter, expectedCardinality) + } + b.Run("reverse iteration with alloc", func(b *testing.B) { + for n := 0; n < b.N; n++ { + counter = 0 + ir := bm.ReverseIterator() + for ir.HasNext() { + ir.Next() + counter++ + } + } + b.StopTimer() + }) + if counter != expectedCardinality { + b.Fatalf("Cardinalities don't match: %d, %d", counter, expectedCardinality) + } + ir := IntReverseIterator{} + + b.Run("reverse iteration", func(b *testing.B) { + for n := 0; n < b.N; n++ { + counter = 0 + ir.Initialize(bm) + for ir.HasNext() { + ir.Next() + counter++ + } + } + b.StopTimer() + }) + if counter != expectedCardinality { + b.Fatalf("Cardinalities don't match: %d, %d", counter, expectedCardinality) + } + b.Run("backward iteration", func(b *testing.B) { + for n := 0; n < b.N; n++ { + counter = 0 + Backward(bm)(func(_ uint32) bool { + counter++ + return true + }) + } + b.StopTimer() + }) + if counter != expectedCardinality { + b.Fatalf("Cardinalities don't match: %d, %d", counter, expectedCardinality) + } + + b.Run("many iteration with alloc", func(b *testing.B) { + for n := 0; n < b.N; n++ { + counter = 0 + buf := make([]uint32, 1024) + im := bm.ManyIterator() + for n := im.NextMany(buf); n != 0; n = im.NextMany(buf) { + counter += uint64(n) + } + } + b.StopTimer() + }) + if counter != expectedCardinality { + b.Fatalf("Cardinalities don't match: %d, %d", counter, expectedCardinality) + } + im := ManyIntIterator{} + buf := make([]uint32, 1024) + + b.Run("many iteration", func(b *testing.B) { + for n := 0; n < b.N; n++ { + counter = 0 + im.Initialize(bm) + for n := im.NextMany(buf); n != 0; n = im.NextMany(buf) { + counter += uint64(n) + } + } + b.StopTimer() + }) + if counter != expectedCardinality { + b.Fatalf("Cardinalities don't match: %d, %d", counter, expectedCardinality) + } + + b.Run("values iteration 1.23", func(b *testing.B) { + for n := 0; n < b.N; n++ { + counter = 0 + for range Values(bm) { + counter++ + } + } + b.StopTimer() + }) + if counter != expectedCardinality { + b.Fatalf("Cardinalities don't match: %d, %d", counter, expectedCardinality) + } + + b.Run("backward iteration 1.23", func(b *testing.B) { + for n := 0; n < b.N; n++ { + counter = 0 + for range Backward(bm) { + counter++ + } + } + b.StopTimer() + }) + if counter != expectedCardinality { + b.Fatalf("Cardinalities don't match: %d, %d", counter, expectedCardinality) + } +} diff --git a/benchmark_test.go b/benchmark_test.go index 755f6b5d..b4dd928e 100644 --- a/benchmark_test.go +++ b/benchmark_test.go @@ -54,6 +54,19 @@ func BenchmarkIteratorAlloc(b *testing.B) { if counter != expectedCardinality { b.Fatalf("Cardinalities don't match: %d, %d", counter, expectedCardinality) } + b.Run("values iteration", func(b *testing.B) { + for n := 0; n < b.N; n++ { + counter = 0 + Values(bm)(func(_ uint32) bool { + counter++ + return true + }) + } + b.StopTimer() + }) + if counter != expectedCardinality { + b.Fatalf("Cardinalities don't match: %d, %d", counter, expectedCardinality) + } b.Run("reverse iteration with alloc", func(b *testing.B) { for n := 0; n < b.N; n++ { counter = 0 @@ -84,6 +97,19 @@ func BenchmarkIteratorAlloc(b *testing.B) { if counter != expectedCardinality { b.Fatalf("Cardinalities don't match: %d, %d", counter, expectedCardinality) } + b.Run("backward iteration", func(b *testing.B) { + for n := 0; n < b.N; n++ { + counter = 0 + Backward(bm)(func(_ uint32) bool { + counter++ + return true + }) + } + b.StopTimer() + }) + if counter != expectedCardinality { + b.Fatalf("Cardinalities don't match: %d, %d", counter, expectedCardinality) + } b.Run("many iteration with alloc", func(b *testing.B) { for n := 0; n < b.N; n++ { diff --git a/iter.go b/iter.go new file mode 100644 index 00000000..71c083d8 --- /dev/null +++ b/iter.go @@ -0,0 +1,23 @@ +package roaring + +func Values(b *Bitmap) func(func(uint32) bool) { + return func(yield func(uint32) bool) { + it := b.Iterator() + for it.HasNext() { + if !yield(it.Next()) { + return + } + } + } +} + +func Backward(b *Bitmap) func(func(uint32) bool) { + return func(yield func(uint32) bool) { + it := b.ReverseIterator() + for it.HasNext() { + if !yield(it.Next()) { + return + } + } + } +} diff --git a/iter123_test.go b/iter123_test.go new file mode 100644 index 00000000..f2c875a5 --- /dev/null +++ b/iter123_test.go @@ -0,0 +1,111 @@ +//go:build go1.23 +// +build go1.23 + +package roaring + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestBackwardCount123(t *testing.T) { + array := []int{2, 63, 64, 65, 4095, 4096, 4097, 4159, 4160, 4161, 5000, 20000, 66666} + for _, testSize := range array { + b := New() + for i := uint32(0); i < uint32(testSize); i++ { + b.Add(i) + } + + count := 0 + for range Values(b) { + count++ + } + + assert.Equal(t, testSize, count) + } +} + +func TestBackward123(t *testing.T) { + t.Run("#1", func(t *testing.T) { + values := []uint32{0, 2, 15, 16, 31, 32, 33, 9999, MaxUint16, MaxUint32} + b := New() + for n := 0; n < len(values); n++ { + b.Add(values[n]) + } + n := len(values) - 1 + for val := range Backward(b) { + assert.EqualValues(t, val, values[n]) + n-- + } + }) + + t.Run("#2", func(t *testing.T) { + b := New() + + count := 0 + for range Backward(b) { + count++ + } + + assert.Equal(t, 0, count) + }) + + t.Run("#3", func(t *testing.T) { + b := New() + b.AddInt(0) + + // only one value zero + for val := range Backward(b) { + assert.EqualValues(t, 0, val) + } + }) + + t.Run("#4", func(t *testing.T) { + b := New() + b.AddInt(9999) + + // only one value 9999 + for val := range Backward(b) { + assert.EqualValues(t, 9999, val) + } + }) + + t.Run("#5", func(t *testing.T) { + b := New() + b.AddInt(MaxUint16) + + // only one value MaxUint16 + for val := range Backward(b) { + assert.EqualValues(t, MaxUint16, val) + } + }) + + t.Run("#6", func(t *testing.T) { + b := New() + b.AddInt(MaxUint32) + + // only one value MaxUint32 + for val := range Backward(b) { + assert.EqualValues(t, MaxUint32, val) + } + }) +} + +func TestValues123(t *testing.T) { + b := New() + + testSize := 5000 + for i := 0; i < testSize; i++ { + b.AddInt(i) + } + + n := 0 + for val := range Values(b) { + assert.Equal(t, uint32(n), val) + n++ + + } + + assert.Equal(t, testSize, n) +} diff --git a/iter_test.go b/iter_test.go new file mode 100644 index 00000000..1efd5b41 --- /dev/null +++ b/iter_test.go @@ -0,0 +1,121 @@ +package roaring + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestBackwardCount(t *testing.T) { + array := []int{2, 63, 64, 65, 4095, 4096, 4097, 4159, 4160, 4161, 5000, 20000, 66666} + for _, testSize := range array { + b := New() + for i := uint32(0); i < uint32(testSize); i++ { + b.Add(i) + } + it := Values(b) + + count := 0 + it(func(_ uint32) bool { + count++ + return true + }) + + assert.Equal(t, testSize, count) + } +} + +func TestBackward(t *testing.T) { + t.Run("#1", func(t *testing.T) { + values := []uint32{0, 2, 15, 16, 31, 32, 33, 9999, MaxUint16} + b := New() + for n := 0; n < len(values); n++ { + b.Add(values[n]) + } + it := Backward(b) + n := len(values) - 1 + + it(func(val uint32) bool { + assert.EqualValues(t, val, values[n]) + n-- + return true + }) + + it = Backward(b) + n = len(values) - 1 + it(func(val uint32) bool { + assert.EqualValues(t, val, values[n]) + assert.True(t, n >= 0) + n-- + return true + }) + }) + + t.Run("#2", func(t *testing.T) { + b := New() + it := Backward(b) + + count := 0 + it(func(_ uint32) bool { + count++ + return true + }) + + assert.Equal(t, 0, count) + }) + + t.Run("#3", func(t *testing.T) { + b := New() + b.AddInt(0) + it := Backward(b) + + // only one value zero + it(func(val uint32) bool { + assert.EqualValues(t, 0, val) + return true + }) + }) + + t.Run("#4", func(t *testing.T) { + b := New() + b.AddInt(9999) + it := Backward(b) + + // only one value 9999 + it(func(val uint32) bool { + assert.EqualValues(t, 9999, val) + return true + }) + }) + + t.Run("#5", func(t *testing.T) { + b := New() + b.AddInt(MaxUint16) + it := Values(b) + + // only one value MaxUint16 + it(func(val uint32) bool { + assert.EqualValues(t, MaxUint16, val) + return true + }) + }) +} + +func TestValues(t *testing.T) { + b := New() + + testSize := 5000 + for i := 0; i < testSize; i++ { + b.AddInt(i) + } + + it := Values(b) + n := 0 + it(func(val uint32) bool { + assert.Equal(t, uint32(n), val) + n++ + return true + }) + + assert.Equal(t, testSize, n) +}