diff --git a/bitmapcontainer.go b/bitmapcontainer.go index 1b705501..3eaad2ba 100644 --- a/bitmapcontainer.go +++ b/bitmapcontainer.go @@ -542,7 +542,7 @@ func (bc *bitmapContainer) orArray(value2 *arrayContainer) container { } func (bc *bitmapContainer) orArrayCardinality(value2 *arrayContainer) int { - answer := 0 + answer := bc.getCardinality() c := value2.getCardinality() for k := 0; k < c; k++ { // branchless: diff --git a/bitmapcontainer_test.go b/bitmapcontainer_test.go index d1feb0fc..fe94049a 100644 --- a/bitmapcontainer_test.go +++ b/bitmapcontainer_test.go @@ -485,3 +485,50 @@ func TestBitmapcontainerNextHasMany(t *testing.T) { assert.Equal(t, 256, result) }) } + +func TestBitmapcontainerOrArrayCardinality(t *testing.T) { + t.Run("Empty Bitmap and Empty Array", func(t *testing.T) { + array := newArrayContainer() + bc := newBitmapContainer() + result := bc.orArrayCardinality(array) + + assert.Equal(t, 0, result) + }) + + t.Run("Populated Bitmap with Empty Array", func(t *testing.T) { + bc := newBitmapContainer() + bc.iaddRange(0, 1024) + array := newArrayContainer() + + result := bc.orArrayCardinality(array) + + assert.Equal(t, 1024, result) + }) + + t.Run("Populated Bitmap with Empty Run Container", func(t *testing.T) { + bc := newBitmapContainer() + bc.iaddRange(0, 1024) + + runC := newRunContainer16() + + result := runC.orBitmapContainerCardinality(bc) + + assert.Equal(t, 1024, result) + + other := newBitmapContainerFromRun(runC) + result = bc.orBitmapCardinality(other) + + assert.Equal(t, 1024, result) + }) + + t.Run("Populated Bitmap with Empty Bitmap", func(t *testing.T) { + bc := newBitmapContainer() + bc.iaddRange(0, 1024) + + other := newBitmapContainer() + + result := bc.orBitmapCardinality(other) + + assert.Equal(t, 1024, result) + }) +} diff --git a/roaring64/parallel64.go b/roaring64/parallel64.go index d6548bba..5dadc8de 100644 --- a/roaring64/parallel64.go +++ b/roaring64/parallel64.go @@ -144,6 +144,8 @@ func (c parChunk) size() int { return c.ra.size() } +// parNaiveStartAt returns the index of the first key that is inclusive between start and last +// Returns the size if there is no such key func parNaiveStartAt(ra *roaringArray64, start uint32, last uint32) int { for idx, key := range ra.keys { if key >= start && key <= last { @@ -170,7 +172,6 @@ func orOnRange(ra1, ra2 *roaringArray64, start, last uint32) *roaringArray64 { key2 = ra2.getKeyAtIndex(idx2) for key1 <= last && key2 <= last { - if key1 < key2 { answer.appendCopy(*ra1, idx1) idx1++ @@ -188,7 +189,7 @@ func orOnRange(ra1, ra2 *roaringArray64, start, last uint32) *roaringArray64 { } else { c1 := ra1.getContainerAtIndex(idx1) - //answer.appendContainer(key1, c1.lazyOR(ra2.getContainerAtIndex(idx2)), false) + // answer.appendContainer(key1, c1.lazyOR(ra2.getContainerAtIndex(idx2)), false) answer.appendContainer(key1, roaring.Or(c1, ra2.getContainerAtIndex(idx2)), false) idx1++ idx2++ @@ -261,7 +262,7 @@ func iorOnRange(ra1, ra2 *roaringArray64, start, last uint32) *roaringArray64 { } else { c1 := ra1.getWritableContainerAtIndex(idx1) - //ra1.containers[idx1] = c1.lazyIOR(ra2.getContainerAtIndex(idx2)) + // ra1.containers[idx1] = c1.lazyIOR(ra2.getContainerAtIndex(idx2)) c1.Or(ra2.getContainerAtIndex(idx2)) ra1.setContainerAtIndex(idx1, c1) diff --git a/roaring64/roaring64_test.go b/roaring64/roaring64_test.go index b0dd1801..69588aec 100644 --- a/roaring64/roaring64_test.go +++ b/roaring64/roaring64_test.go @@ -52,6 +52,29 @@ func TestIssue266(t *testing.T) { } } +func TestParOr64(t *testing.T) { + t.Run("Test 1", func(t *testing.T) { + a := BitmapOf(0, 1, 2, 3, 4) + b := BitmapOf(5, 6, 7, 8, 9, 10) + c := BitmapOf(11, 12, 13, 14, 15) + d := ParOr(0, a, b, c) + expected := BitmapOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15) + assert.True(t, d.Equals(expected)) + }) + + t.Run("Test 2", func(t *testing.T) { + a := BitmapOf(0, 1, 2, 3, 4) + + offset1 := uint64(2 << 16) + b := BitmapOf(offset1, offset1+1, offset1+2, offset1+3, offset1+5) + offset2 := uint64(4 << 16) + c := BitmapOf(offset2, offset2+1, offset2+2, offset2+3, offset2+5) + d := ParOr(0, a, b, c) + expected := BitmapOf(0, 1, 2, 3, 4, offset1, offset1+1, offset1+2, offset1+3, offset1+5, offset2, offset2+1, offset2+2, offset2+3, offset2+5) + assert.True(t, d.Equals(expected)) + }) +} + func TestRoaringRangeEnd(t *testing.T) { r := New() r.Add(roaring.MaxUint32) diff --git a/roaring64/roaringarray64.go b/roaring64/roaringarray64.go index 0031ed54..629ad751 100644 --- a/roaring64/roaringarray64.go +++ b/roaring64/roaringarray64.go @@ -335,6 +335,15 @@ func (ra *roaringArray64) hasRunCompression() bool { return false } +/** + * Find the smallest integer index strictly larger than pos such that array[index].key>=min. If none can + * be found, return size. Based on code by O. Kaser. + * + * @param min minimal value + * @param pos index to exceed + * @return the smallest index greater than pos such that array[index].key is at least as large as + * min, or size if it is not possible. + */ func (ra *roaringArray64) advanceUntil(min uint32, pos int) int { lower := pos + 1 diff --git a/roaring64/roaringarray64_test.go b/roaring64/roaringarray64_test.go new file mode 100644 index 00000000..8b3a696a --- /dev/null +++ b/roaring64/roaringarray64_test.go @@ -0,0 +1,130 @@ +package roaring64 + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestRoaringArray64AdvanceUntil(t *testing.T) { + bitmap := New() + low := uint64(1) << 32 + mid := uint64(2) << 32 + high := uint64(3) << 32 + bitmap.AddRange(uint64(low)-1, uint64(low)+2) + bitmap.AddRange(uint64(mid)-1, uint64(mid)+2) + bitmap.AddRange(uint64(high)-1, uint64(high)+2) + + assert.Equal(t, 0, bitmap.highlowcontainer.advanceUntil(0, -1)) + assert.Equal(t, 1, bitmap.highlowcontainer.advanceUntil(1, -1)) + assert.Equal(t, 2, bitmap.highlowcontainer.advanceUntil(2, -1)) + assert.Equal(t, 3, bitmap.highlowcontainer.advanceUntil(3, -1)) + assert.Equal(t, 4, bitmap.highlowcontainer.advanceUntil(4, -1)) + + assert.Equal(t, 1, bitmap.highlowcontainer.advanceUntil(0, 0)) + assert.Equal(t, 2, bitmap.highlowcontainer.advanceUntil(1, 1)) + assert.Equal(t, 3, bitmap.highlowcontainer.advanceUntil(2, 2)) + assert.Equal(t, 4, bitmap.highlowcontainer.advanceUntil(3, 3)) + assert.Equal(t, 5, bitmap.highlowcontainer.advanceUntil(4, 4)) +} + +func TestCopies(t *testing.T) { + tests := []struct { + name string + cow1 bool + cow2 bool + }{ + {"AppendCopiesAfterCoW", true, true}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + r1 := uint64(1) << 32 + r2 := uint64(2) << 32 + r3 := uint64(3) << 32 + r4 := uint64(4) << 32 + + bitmap1 := New() + bitmap2 := New() + bitmap1.SetCopyOnWrite(test.cow1) + bitmap2.SetCopyOnWrite(test.cow2) + + bitmap2.AddRange(uint64(r1)-1, uint64(r1)+2) + bitmap2.AddRange(uint64(r2)-1, uint64(r2)+2) + bitmap2.AddRange(uint64(r3)-1, uint64(r3)+2) + bitmap2.AddRange(uint64(r4)-1, uint64(r4)+2) + + assert.False(t, bitmap1.Contains(uint64(r2))) + assert.False(t, bitmap1.Contains(uint64(r3))) + assert.Equal(t, 0, len(bitmap1.highlowcontainer.keys)) + bitmap1.highlowcontainer.appendCopiesAfter(bitmap2.highlowcontainer, 0) + assert.Equal(t, 4, len(bitmap1.highlowcontainer.keys)) + assert.True(t, bitmap1.Contains(uint64(r2))) + assert.True(t, bitmap1.Contains(uint64(r3))) + + for idx1, c1 := range bitmap1.highlowcontainer.containers { + for idx2, c2 := range bitmap2.highlowcontainer.containers { + // idx+1 is required because appendCopiesAfter starts at key 1 + if idx1+1 == idx2 { + if test.cow1 && test.cow2 { + assert.True(t, c1 == c2) + } else { + assert.False(t, c1 == c2) + } + } + } + } + }) + } + + tests = []struct { + name string + cow1 bool + cow2 bool + }{ + {"AppendCopiesUntilCoW", true, true}, + {"AppendCopiesUntilCoW", false, false}, + {"AppendCopiesUntilMixedCoW", true, false}, + {"AppendCopiesUntilMixedCoW", false, true}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + r1 := uint64(1) << 32 + r2 := uint64(2) << 32 + r3 := uint64(3) << 32 + r4 := uint64(4) << 32 + key := highbits(uint64(r4)) + + bitmap1 := New() + bitmap2 := New() + bitmap1.SetCopyOnWrite(test.cow1) + bitmap2.SetCopyOnWrite(test.cow2) + + bitmap2.AddRange(uint64(r1)-1, uint64(r1)+2) + bitmap2.AddRange(uint64(r2)-1, uint64(r2)+2) + bitmap2.AddRange(uint64(r3)-1, uint64(r3)+2) + bitmap2.AddRange(uint64(r4)-1, uint64(r4)+2) + + assert.False(t, bitmap1.Contains(uint64(r2))) + assert.False(t, bitmap1.Contains(uint64(r3))) + assert.Equal(t, 0, len(bitmap1.highlowcontainer.keys)) + bitmap1.highlowcontainer.appendCopiesUntil(bitmap2.highlowcontainer, key) + assert.Equal(t, 4, len(bitmap1.highlowcontainer.keys)) + assert.True(t, bitmap1.Contains(uint64(r2))) + assert.True(t, bitmap1.Contains(uint64(r3))) + + for idx1, c1 := range bitmap1.highlowcontainer.containers { + for idx2, c2 := range bitmap2.highlowcontainer.containers { + if idx1 == idx2 { + if test.cow1 && test.cow2 { + assert.True(t, c1 == c2) + } else { + assert.False(t, c1 == c2) + } + } + } + } + }) + } +} diff --git a/roaring_test.go b/roaring_test.go index e5a7d032..44d1da6d 100644 --- a/roaring_test.go +++ b/roaring_test.go @@ -406,6 +406,42 @@ func TestFastCard(t *testing.T) { assert.Equal(t, bm.GetCardinality(), bm2.OrCardinality(bm)) } +func TestFastCardUnequalKeys(t *testing.T) { + // These tests will excercise the interior code branches of OrCardinality + + t.Run("Merge small into large", func(t *testing.T) { + bm := NewBitmap() + bm.AddRange(0, 1024) + bm2 := NewBitmap() + start := uint64(2 << 16) + bm2.AddRange(start, start+3) + + assert.Equal(t, uint64(1027), bm2.OrCardinality(bm)) + }) + t.Run("Merge large into small", func(t *testing.T) { + bm := NewBitmap() + bm.AddRange(0, 1024) + bm2 := NewBitmap() + start := uint64(2 << 16) + bm2.AddRange(start, start+3) + + assert.Equal(t, uint64(1027), bm.OrCardinality(bm2)) + }) + + t.Run("Merge large into small same keyrange start", func(t *testing.T) { + bm := NewBitmap() + start := uint64(2 << 16) + bm.AddRange(0, 1024) + bm.AddRange(start, start+3) + + bm2 := NewBitmap() + bm2.AddRange(0, 512) + bm2.AddRange(start, start+3) + + assert.Equal(t, uint64(1027), bm.OrCardinality(bm2)) + }) +} + func TestIntersects1(t *testing.T) { bm := NewBitmap() bm.Add(1) diff --git a/roaringarray_test.go b/roaringarray_test.go new file mode 100644 index 00000000..bf45b1e0 --- /dev/null +++ b/roaringarray_test.go @@ -0,0 +1,29 @@ +package roaring + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestRoaringArrayAdvanceUntil(t *testing.T) { + bitmap := New() + low := 1 << 16 + mid := 2 << 16 + high := 3 << 16 + bitmap.AddRange(uint64(low)-1, uint64(low)+2) + bitmap.AddRange(uint64(mid)-1, uint64(mid)+2) + bitmap.AddRange(uint64(high)-1, uint64(high)+2) + + assert.Equal(t, 0, bitmap.highlowcontainer.advanceUntil(0, -1)) + assert.Equal(t, 1, bitmap.highlowcontainer.advanceUntil(1, -1)) + assert.Equal(t, 2, bitmap.highlowcontainer.advanceUntil(2, -1)) + assert.Equal(t, 3, bitmap.highlowcontainer.advanceUntil(3, -1)) + assert.Equal(t, 4, bitmap.highlowcontainer.advanceUntil(4, -1)) + + assert.Equal(t, 1, bitmap.highlowcontainer.advanceUntil(0, 0)) + assert.Equal(t, 2, bitmap.highlowcontainer.advanceUntil(1, 1)) + assert.Equal(t, 3, bitmap.highlowcontainer.advanceUntil(2, 2)) + assert.Equal(t, 4, bitmap.highlowcontainer.advanceUntil(3, 3)) + assert.Equal(t, 5, bitmap.highlowcontainer.advanceUntil(4, 4)) +} diff --git a/runcontainer_test.go b/runcontainer_test.go index 296c3d6c..d3a2e60c 100644 --- a/runcontainer_test.go +++ b/runcontainer_test.go @@ -2628,6 +2628,101 @@ func TestIntervalValidationsPassing(t *testing.T) { assert.NoError(t, rc.validate()) } +func TestRunContainerUnionCardinality(t *testing.T) { + t.Run("Two Empty Runs", func(t *testing.T) { + first := runContainer16{} + second := runContainer16{} + result := first.unionCardinality(&second) + assert.Equal(t, uint(0), result) + }) + + t.Run("First Run Empty", func(t *testing.T) { + first := runContainer16{} + second := runContainer16{} + second.iaddRange(0, 1024) + result := first.unionCardinality(&second) + assert.Equal(t, uint(1024), result) + }) + + t.Run("Second Run Empty", func(t *testing.T) { + first := runContainer16{} + second := runContainer16{} + first.iaddRange(0, 1024) + result := first.unionCardinality(&second) + assert.Equal(t, uint(1024), result) + }) + + t.Run("Disjoint Ranges", func(t *testing.T) { + first := runContainer16{} + first.iaddRange(512, 1024) + second := runContainer16{} + second.iaddRange(0, 256) + result := first.unionCardinality(&second) + assert.Equal(t, uint(256+512), result) + }) + + t.Run("Complete Overlap", func(t *testing.T) { + first := runContainer16{} + first.iaddRange(0, 256) + second := runContainer16{} + second.iaddRange(0, 256) + result := first.unionCardinality(&second) + assert.Equal(t, uint(256), result) + }) +} + +func TestRunContainerIntersectCardinality(t *testing.T) { + t.Run("Two Empty Runs", func(t *testing.T) { + first := runContainer16{} + second := runContainer16{} + result := first.intersectCardinality(&second) + assert.Equal(t, 0, result) + }) + + t.Run("First Run Empty", func(t *testing.T) { + first := runContainer16{} + second := runContainer16{} + second.iaddRange(0, 1024) + result := first.intersectCardinality(&second) + assert.Equal(t, 0, result) + }) + + t.Run("Second Run Empty", func(t *testing.T) { + first := runContainer16{} + second := runContainer16{} + first.iaddRange(0, 1024) + result := first.intersectCardinality(&second) + assert.Equal(t, 0, result) + }) + + t.Run("Disjoint Ranges", func(t *testing.T) { + first := runContainer16{} + first.iaddRange(512, 1024) + second := runContainer16{} + second.iaddRange(0, 256) + result := first.intersectCardinality(&second) + assert.Equal(t, 0, result) + }) + + t.Run("Complete Overlap", func(t *testing.T) { + first := runContainer16{} + first.iaddRange(0, 256) + second := runContainer16{} + second.iaddRange(0, 256) + result := first.intersectCardinality(&second) + assert.Equal(t, 256, result) + }) + + t.Run("Single Element Intersection", func(t *testing.T) { + first := runContainer16{} + first.iaddRange(0, 257) + second := runContainer16{} + second.iaddRange(256, 512) + result := first.intersectCardinality(&second) + assert.Equal(t, 1, result) + }) +} + // go test -bench BenchmarkShortIteratorAdvance -run - func BenchmarkShortIteratorAdvanceRuntime(b *testing.B) { benchmarkContainerIteratorAdvance(b, newRunContainer16())