diff --git a/arraycontainer.go b/arraycontainer.go index 602069b6..eecb90cb 100644 --- a/arraycontainer.go +++ b/arraycontainer.go @@ -65,10 +65,26 @@ func (ac *arrayContainer) minimum() uint16 { return ac.content[0] // assume not empty } +func (ac *arrayContainer) safeMinimum() (uint16, error) { + if len(ac.content) == 0 { + return 0, errors.New("empty array") + } + + return ac.minimum(), nil +} + func (ac *arrayContainer) maximum() uint16 { return ac.content[len(ac.content)-1] // assume not empty } +func (ac *arrayContainer) safeMaximum() (uint16, error) { + if len(ac.content) == 0 { + return 0, errors.New("empty array") + } + + return ac.maximum(), nil +} + func (ac *arrayContainer) getSizeInBytes() int { return ac.getCardinality() * 2 } @@ -975,35 +991,40 @@ func (ac *arrayContainer) realloc(size int) { // Ex: target=4 ac=[2,3,4,6,7] returns 4 // Ex: target=5 ac=[2,3,4,6,7] returns 4 // Ex: target=6 ac=[2,3,4,6,7] returns 6 +// Ex: target=8 ac=[2,3,4,6,7] returns 7 // Ex: target=1 ac=[2,3,4,6,7] returns -1 +// Ex: target=0 ac=[2,3,4,6,7] returns -1 func (ac *arrayContainer) previousValue(target uint16) int { result := binarySearchUntil(ac.content, target) + if result.index == len(ac.content) { + return int(ac.maximum()) + } + if result.outOfBounds() { return -1 } + return int(result.value) } // previousAbsentValue returns either the target if not found or the next larger missing value. // If the target is out of bounds a -1 is returned -// Ex: target=4 ac=[1,2,3,4,6,7] returns -1 +// Ex: target=4 ac=[1,2,3,4,6,7] returns 0 // Ex: target=5 ac=[1,2,3,4,6,7] returns 5 // Ex: target=6 ac=[1,2,3,4,6,7] returns 5 -// Ex: target=8 ac=[1,2,3,4,6,7] returns -1 +// Ex: target=8 ac=[1,2,3,4,6,7] returns 8 func (ac *arrayContainer) previousAbsentValue(target uint16) int { cardinality := len(ac.content) if cardinality == 0 { - return -1 + return int(target) } - if target <= ac.minimum() { - return -1 - } if target > ac.maximum() { - return -1 + return int(target) } + result := binarySearchPast(ac.content, target) if result.notFound() { @@ -1017,7 +1038,7 @@ func (ac *arrayContainer) previousAbsentValue(target uint16) int { } } - low := 0 + low := -1 high := result.index // This uses the pigeon-hole principle. @@ -1037,11 +1058,8 @@ func (ac *arrayContainer) previousAbsentValue(target uint16) int { } } - if low == 0 { - if high == 1 { - return -1 - } - return int(ac.content[high] - 1) + if high == 0 { + return int(ac.minimum()) - 1 } return int(ac.content[high] - 1) @@ -1051,16 +1069,18 @@ func (ac *arrayContainer) previousAbsentValue(target uint16) int { // If the target is out of bounds a -1 is returned // Ex: target=4 ac=[1,2,3,4,6,7] returns 5 // Ex: target=5 ac=[1,2,3,4,6,7] returns 5 -// Ex: target=0 ac=[1,2,3,4,6,7] returns -1 -// Ex: target=8 ac=[1,2,3,4,6,7] returns -1 +// Ex: target=0 ac=[1,2,3,4,6,7] returns 0 +// Ex: target=8 ac=[1,2,3,4,6,7] returns 8 func (ac *arrayContainer) nextAbsentValue(target uint16) int { cardinality := len(ac.content) - if target < ac.minimum() { - return -1 + + if cardinality == 0 { + return int(target) } - if target > ac.maximum() { - return -1 + if target < ac.minimum() { + return int(target) } + result := binarySearchPast(ac.content, target) if result.notFound() { @@ -1102,26 +1122,33 @@ func (ac *arrayContainer) nextAbsentValue(target uint16) int { // nextValue returns either the target if found or the next larger value. // if the target is out of bounds a -1 is returned +// // Ex: target=4 ac=[1,2,3,4,6,7] returns 4 // Ex: target=5 ac=[1,2,3,4,6,7] returns 6 // Ex: target=6 ac=[1,2,3,4,6,7] returns 6 +// Ex: target=0 ac=[1,2,3,4,6,7] returns 1 +// Ex: target=100 ac=[1,2,3,4,6,7] returns -1 func (ac *arrayContainer) nextValue(target uint16) int { cardinality := len(ac.content) if cardinality == 0 { return -1 } - if target < ac.minimum() { - return -1 - } - if target > ac.maximum() { - return -1 - } + //if target < ac.minimum() { + // return -1 + //} + //if target > ac.maximum() { + // return -1 + // } result := binarySearchUntil(ac.content, target) if result.exactMatch { return int(result.value) } + + if !result.exactMatch && result.index == -1 { + return int(ac.content[0]) + } if result.outOfBounds() { return -1 } diff --git a/arraycontainer_test.go b/arraycontainer_test.go index e797107b..112c3103 100644 --- a/arraycontainer_test.go +++ b/arraycontainer_test.go @@ -435,92 +435,178 @@ func TestArrayContainerResetTo(t *testing.T) { }) } -func TestNextPrevious(t *testing.T) { - t.Run("small range", func(t *testing.T) { +func TestNextValueArray(t *testing.T) { + t.Run("Java Port 1", func(t *testing.T) { + // [Example 1] https://github.com/RoaringBitmap/RoaringBitmap/blob/5235aa62c32fa3bf7fae40a562e3edc75f61be4e/RoaringBitmap/src/test/java/org/roaringbitmap/buffer/TestMappeableArrayContainer.java#L495 ac := newArrayContainer() - ac.iaddRange(1, 10) - assert.Equal(t, 1, ac.nextValue(1)) - assert.Equal(t, 6, ac.nextValue(6)) - assert.Equal(t, 9, ac.nextValue(9)) - assert.Equal(t, -1, ac.nextValue(10)) - assert.Equal(t, -1, ac.nextValue(20)) - ac.iaddRange(12, 20) - // 10 and 11 are now missing + ac.iaddRange(64, 129) + assert.Equal(t, 64, ac.nextValue(0)) + assert.Equal(t, 64, ac.nextValue(64)) + assert.Equal(t, 65, ac.nextValue(65)) + assert.Equal(t, 128, ac.nextValue(128)) + assert.Equal(t, -1, ac.nextValue(129)) + assert.Equal(t, -1, ac.nextValue(5000)) + }) - assert.Equal(t, -1, ac.previousValue(0)) - assert.Equal(t, 1, ac.previousValue(1)) - assert.Equal(t, 9, ac.previousValue(11)) - assert.Equal(t, -1, ac.previousValue(22)) - - assert.Equal(t, -1, ac.nextAbsentValue(0)) - assert.Equal(t, 10, ac.nextAbsentValue(1)) - assert.Equal(t, 10, ac.nextAbsentValue(9)) - assert.Equal(t, 10, ac.nextAbsentValue(10)) - assert.Equal(t, 11, ac.nextAbsentValue(11)) - assert.Equal(t, 20, ac.nextAbsentValue(12)) - assert.Equal(t, -1, ac.nextAbsentValue(21)) - - assert.Equal(t, -1, ac.previousAbsentValue(0)) - assert.Equal(t, -1, ac.previousAbsentValue(1)) - assert.Equal(t, -1, ac.previousAbsentValue(9)) - assert.Equal(t, 10, ac.previousAbsentValue(10)) - assert.Equal(t, 11, ac.previousAbsentValue(11)) - assert.Equal(t, 11, ac.previousAbsentValue(12)) - assert.Equal(t, 11, ac.previousAbsentValue(15)) + t.Run("Java Port 2", func(t *testing.T) { + // [Example 2] https://github.com/RoaringBitmap/RoaringBitmap/blob/5235aa62c32fa3bf7fae40a562e3edc75f61be4e/RoaringBitmap/src/test/java/org/roaringbitmap/buffer/TestMappeableArrayContainer.java#L507 + ac := newArrayContainer() + ac.iaddRange(64, 129) + ac.iaddRange(256, 321) + assert.Equal(t, 64, ac.nextValue(0)) + assert.Equal(t, 64, ac.nextValue(63)) + assert.Equal(t, 64, ac.nextValue(64)) + assert.Equal(t, 65, ac.nextValue(65)) + assert.Equal(t, 128, ac.nextValue(128)) + assert.Equal(t, 256, ac.nextValue(129)) + assert.Equal(t, -1, ac.nextValue(512)) + }) + + t.Run("Java Port 3", func(t *testing.T) { + // [Example 3] https://github.com/RoaringBitmap/RoaringBitmap/blob/5235aa62c32fa3bf7fae40a562e3edc75f61be4e/RoaringBitmap/src/test/java/org/roaringbitmap/buffer/TestMappeableArrayContainer.java#L525 + ac := newArrayContainer() + ac.iaddRange(64, 129) + ac.iaddRange(200, 501) + ac.iaddRange(5000, 5201) + + assert.Equal(t, 64, ac.nextValue(0)) + assert.Equal(t, 64, ac.nextValue(63)) + assert.Equal(t, 64, ac.nextValue(64)) + assert.Equal(t, 65, ac.nextValue(65)) + assert.Equal(t, 128, ac.nextValue(128)) + assert.Equal(t, 200, ac.nextValue(129)) + assert.Equal(t, 200, ac.nextValue(199)) + assert.Equal(t, 200, ac.nextValue(200)) + assert.Equal(t, 250, ac.nextValue(250)) + assert.Equal(t, 5000, ac.nextValue(2500)) + assert.Equal(t, 5000, ac.nextValue(5000)) + assert.Equal(t, 5200, ac.nextValue(5200)) + assert.Equal(t, -1, ac.nextValue(5201)) + }) +} + +func TestNextAbsentValueArray(t *testing.T) { + t.Run("Java Port 1", func(t *testing.T) { + // [Java 1] https://github.com/RoaringBitmap/RoaringBitmap/blob/5235aa62c32fa3bf7fae40a562e3edc75f61be4e/RoaringBitmap/src/test/java/org/roaringbitmap/TestArrayContainer.java#L850 + ac := newArrayContainer() + ac.iaddRange(64, 129) + assert.Equal(t, 0, ac.nextAbsentValue(0)) + assert.Equal(t, 63, ac.nextAbsentValue(63)) + assert.Equal(t, 129, ac.nextAbsentValue(64)) + assert.Equal(t, 129, ac.nextAbsentValue(65)) + assert.Equal(t, 129, ac.nextAbsentValue(128)) + assert.Equal(t, 129, ac.nextAbsentValue(129)) }) - t.Run("larger range", func(t *testing.T) { + t.Run("Java Port 2", func(t *testing.T) { + // [Example 2] https://github.com/RoaringBitmap/RoaringBitmap/blob/5235aa62c32fa3bf7fae40a562e3edc75f61be4e/RoaringBitmap/src/test/java/org/roaringbitmap/TestArrayContainer.java#L861 ac := newArrayContainer() - ac.iaddRange(1, 10) - ac.iaddRange(12, 20) - ac.iaddRange(62, 80) - ac.iaddRange(125, 170) - - assert.Equal(t, 62, ac.nextValue(20)) - assert.Equal(t, 1, ac.nextValue(1)) - assert.Equal(t, 6, ac.nextValue(6)) - assert.Equal(t, 9, ac.nextValue(9)) - assert.Equal(t, 12, ac.nextValue(10)) - assert.Equal(t, 62, ac.nextValue(20)) - assert.Equal(t, 62, ac.nextValue(43)) - assert.Equal(t, 62, ac.nextValue(62)) - assert.Equal(t, 125, ac.nextValue(80)) - assert.Equal(t, 125, ac.nextValue(125)) + ac.iaddRange(64, 129) + ac.iaddRange(200, 501) + ac.iaddRange(5000, 5201) + assert.Equal(t, 0, ac.nextAbsentValue(0)) + assert.Equal(t, 63, ac.nextAbsentValue(63)) + assert.Equal(t, 129, ac.nextAbsentValue(64)) + assert.Equal(t, 129, ac.nextAbsentValue(65)) + assert.Equal(t, 129, ac.nextAbsentValue(128)) + assert.Equal(t, 129, ac.nextAbsentValue(129)) + assert.Equal(t, 199, ac.nextAbsentValue(199)) + assert.Equal(t, 501, ac.nextAbsentValue(200)) + assert.Equal(t, 501, ac.nextAbsentValue(250)) + assert.Equal(t, 2500, ac.nextAbsentValue(2500)) + assert.Equal(t, 5201, ac.nextAbsentValue(5000)) + assert.Equal(t, 5201, ac.nextAbsentValue(5200)) + }) + t.Run("Java Port 3", func(t *testing.T) { + // [Java 3] https://github.com/RoaringBitmap/RoaringBitmap/blob/5235aa62c32fa3bf7fae40a562e3edc75f61be4e/RoaringBitmap/src/test/java/org/roaringbitmap/TestArrayContainer.java#L878 + ac := newArrayContainer() + for i := 0; i < 1000; i++ { + assert.Equal(t, i, ac.nextAbsentValue(uint16(i))) + } + }) +} + +func TestPreviousValueArray(t *testing.T) { + t.Run("Java Port 1", func(t *testing.T) { + // [Example 1] https://github.com/RoaringBitmap/RoaringBitmap/blob/5235aa62c32fa3bf7fae40a562e3edc75f61be4e/RoaringBitmap/src/test/java/org/roaringbitmap/TestArrayContainer.java#L721 + ac := newArrayContainer() + ac.iaddRange(64, 129) assert.Equal(t, -1, ac.previousValue(0)) - assert.Equal(t, 1, ac.previousValue(1)) - assert.Equal(t, 9, ac.previousValue(11)) - assert.Equal(t, 19, ac.previousValue(22)) - assert.Equal(t, 19, ac.previousValue(61)) - assert.Equal(t, 62, ac.previousValue(62)) - assert.Equal(t, 79, ac.previousValue(79)) - assert.Equal(t, 79, ac.previousValue(80)) - assert.Equal(t, 79, ac.previousValue(124)) - assert.Equal(t, 125, ac.previousValue(125)) - - assert.Equal(t, -1, ac.nextAbsentValue(0)) - assert.Equal(t, 10, ac.nextAbsentValue(1)) - assert.Equal(t, 10, ac.nextAbsentValue(9)) - assert.Equal(t, 10, ac.nextAbsentValue(10)) - assert.Equal(t, 11, ac.nextAbsentValue(11)) - assert.Equal(t, 20, ac.nextAbsentValue(12)) - assert.Equal(t, 21, ac.nextAbsentValue(21)) - assert.Equal(t, 61, ac.nextAbsentValue(61)) - assert.Equal(t, 80, ac.nextAbsentValue(62)) - assert.Equal(t, 80, ac.nextAbsentValue(80)) - assert.Equal(t, 90, ac.nextAbsentValue(90)) - assert.Equal(t, 170, ac.nextAbsentValue(125)) - - assert.Equal(t, -1, ac.previousAbsentValue(0)) - assert.Equal(t, -1, ac.previousAbsentValue(1)) - assert.Equal(t, -1, ac.previousAbsentValue(9)) - assert.Equal(t, 10, ac.previousAbsentValue(10)) - assert.Equal(t, 11, ac.previousAbsentValue(11)) - assert.Equal(t, 11, ac.previousAbsentValue(12)) - assert.Equal(t, 11, ac.previousAbsentValue(15)) - assert.Equal(t, 20, ac.previousAbsentValue(20)) - assert.Equal(t, 61, ac.previousAbsentValue(62)) - assert.Equal(t, 90, ac.previousAbsentValue(90)) + assert.Equal(t, -1, ac.previousValue(63)) + assert.Equal(t, 64, ac.previousValue(64)) + assert.Equal(t, 65, ac.previousValue(65)) + assert.Equal(t, 128, ac.previousValue(128)) + assert.Equal(t, 128, ac.previousValue(129)) + }) + + t.Run("Java Port 2", func(t *testing.T) { + // [Example 2] https://github.com/RoaringBitmap/RoaringBitmap/blob/5235aa62c32fa3bf7fae40a562e3edc75f61be4e/RoaringBitmap/src/test/java/org/roaringbitmap/TestArrayContainer.java#L733 + ac := newArrayContainer() + ac.iaddRange(64, 129) + ac.iaddRange(200, 501) + ac.iaddRange(5000, 5201) + assert.Equal(t, -1, ac.previousValue(0)) + assert.Equal(t, -1, ac.previousValue(63)) + assert.Equal(t, 64, ac.previousValue(64)) + assert.Equal(t, 65, ac.previousValue(65)) + assert.Equal(t, 128, ac.previousValue(128)) + assert.Equal(t, 128, ac.previousValue(129)) + assert.Equal(t, 128, ac.previousValue(199)) + assert.Equal(t, 200, ac.previousValue(200)) + assert.Equal(t, 250, ac.previousValue(250)) + assert.Equal(t, 500, ac.previousValue(2500)) + assert.Equal(t, 5000, ac.previousValue(5000)) + assert.Equal(t, 5200, ac.previousValue(5200)) + }) + + t.Run("Java Port 3", func(t *testing.T) { + // [Example 3] https://github.com/RoaringBitmap/RoaringBitmap/blob/5235aa62c32fa3bf7fae40a562e3edc75f61be4e/RoaringBitmap/src/test/java/org/roaringbitmap/TestArrayContainer.java#L751 + ac := newArrayContainer() + ac.iaddRange(64, 129) + assert.Equal(t, -1, ac.previousValue(5)) + }) +} + +func TestPreviousAbsentValueArray(t *testing.T) { + t.Run("Java Port 1", func(t *testing.T) { + // [Example 1] https://github.com/RoaringBitmap/RoaringBitmap/blob/5235aa62c32fa3bf7fae40a562e3edc75f61be4e/RoaringBitmap/src/test/java/org/roaringbitmap/TestArrayContainer.java#L793 + ac := newArrayContainer() + ac.iaddRange(64, 129) + assert.Equal(t, 0, ac.previousAbsentValue(0)) + assert.Equal(t, 63, ac.previousAbsentValue(63)) + assert.Equal(t, 63, ac.previousAbsentValue(64)) + assert.Equal(t, 63, ac.previousAbsentValue(65)) + assert.Equal(t, 63, ac.previousAbsentValue(128)) + assert.Equal(t, 129, ac.previousAbsentValue(129)) + }) + + t.Run("Java Port 2", func(t *testing.T) { + // [Example 2] https://github.com/RoaringBitmap/RoaringBitmap/blob/5235aa62c32fa3bf7fae40a562e3edc75f61be4e/RoaringBitmap/src/test/java/org/roaringbitmap/TestArrayContainer.java#L804 + ac := newArrayContainer() + ac.iaddRange(64, 129) + ac.iaddRange(200, 500) + ac.iaddRange(5000, 5201) + assert.Equal(t, 0, ac.previousAbsentValue(0)) + assert.Equal(t, 63, ac.previousAbsentValue(63)) + assert.Equal(t, 63, ac.previousAbsentValue(64)) + assert.Equal(t, 63, ac.previousAbsentValue(65)) + assert.Equal(t, 63, ac.previousAbsentValue(128)) + assert.Equal(t, 129, ac.previousAbsentValue(129)) + assert.Equal(t, 199, ac.previousAbsentValue(199)) + assert.Equal(t, 199, ac.previousAbsentValue(200)) + assert.Equal(t, 199, ac.previousAbsentValue(250)) + assert.Equal(t, 2500, ac.previousAbsentValue(2500)) + assert.Equal(t, 4999, ac.previousAbsentValue(5000)) + assert.Equal(t, 4999, ac.previousAbsentValue(5200)) + }) + + t.Run("Java Port 3", func(t *testing.T) { + // [Example 3] https://github.com/RoaringBitmap/RoaringBitmap/blob/5235aa62c32fa3bf7fae40a562e3edc75f61be4e/RoaringBitmap/src/test/java/org/roaringbitmap/TestArrayContainer.java#L821 + ac := newArrayContainer() + for i := 0; i < 1000; i++ { + assert.Equal(t, i, ac.previousAbsentValue(uint16(i))) + } }) } diff --git a/bitmapcontainer.go b/bitmapcontainer.go index d8cb04f6..4463d7ae 100644 --- a/bitmapcontainer.go +++ b/bitmapcontainer.go @@ -1,6 +1,7 @@ package roaring import ( + "errors" "fmt" "math/bits" "unsafe" @@ -57,6 +58,17 @@ func (bc *bitmapContainer) minimum() uint16 { return MaxUint16 } +func (bc *bitmapContainer) safeMinimum() (uint16, error) { + if len(bc.bitmap) == 0 { + return 0, errors.New("Empty bitmap") + } + val := bc.minimum() + if val == MaxUint16 { + return 0, errors.New("Empty bitmap") + } + return val, nil +} + // i should be non-zero func clz(i uint64) int { n := 1 @@ -95,6 +107,17 @@ func (bc *bitmapContainer) maximum() uint16 { return uint16(0) } +func (bc *bitmapContainer) safeMaximum() (uint16, error) { + if len(bc.bitmap) == 0 { + return 0, errors.New("Empty bitmap") + } + val := bc.maximum() + if val == uint16(0) { + return 0, errors.New("Empty bitmap") + } + return val, nil +} + func (bc *bitmapContainer) iterate(cb func(x uint16) bool) bool { iterator := bitmapContainerShortIterator{bc, bc.NextSetBit(0)} diff --git a/bitmapcontainer_test.go b/bitmapcontainer_test.go index 59e86194..bbb6dd1f 100644 --- a/bitmapcontainer_test.go +++ b/bitmapcontainer_test.go @@ -329,14 +329,6 @@ func TestBitmapContainerIAndNot(t *testing.T) { require.Equal(t, 3, bc.getCardinality()) } -func TestPreviousNext(t *testing.T) { - clean := newBitmapContainer() - clean.iadd(1) - clean.contains(1) - clean.iadd(3) - clean.contains(3) -} - func TestPreviousNexts(t *testing.T) { bc := newBitmapContainer() bc.iadd(10) @@ -348,57 +340,76 @@ func TestPreviousNexts(t *testing.T) { // Another 64 division mod boundary bc.iadd(129) - assert.Equal(t, 10, bc.nextValue(uint16(0))) - assert.Equal(t, 10, bc.nextValue(uint16(5))) - assert.Equal(t, 10, bc.nextValue(uint16(10))) - assert.Equal(t, 12, bc.nextValue(uint16(11))) - assert.Equal(t, 12, bc.nextValue(uint16(12))) - assert.Equal(t, 13, bc.nextValue(uint16(13))) - assert.Equal(t, 50, bc.nextValue(uint16(14))) - assert.Equal(t, 55, bc.nextValue(uint16(55))) - assert.Equal(t, 100, bc.nextValue(uint16(61))) - assert.Equal(t, 100, bc.nextValue(uint16(100))) - assert.Equal(t, 129, bc.nextValue(uint16(101))) - assert.Equal(t, 129, bc.nextValue(uint16(129))) - assert.Equal(t, -1, bc.nextValue(uint16(130))) - - assert.Equal(t, -1, bc.previousValue(uint16(0))) - assert.Equal(t, -1, bc.previousValue(uint16(1))) - assert.Equal(t, -1, bc.previousValue(uint16(2))) - assert.Equal(t, -1, bc.previousValue(uint16(5))) - assert.Equal(t, 10, bc.previousValue(uint16(10))) - assert.Equal(t, 10, bc.previousValue(uint16(11))) - assert.Equal(t, 12, bc.previousValue(uint16(12))) - assert.Equal(t, 13, bc.previousValue(uint16(13))) - assert.Equal(t, 13, bc.previousValue(uint16(14))) - assert.Equal(t, 55, bc.previousValue(uint16(55))) - assert.Equal(t, 59, bc.previousValue(uint16(61))) - assert.Equal(t, 100, bc.previousValue(uint16(101))) - - assert.Equal(t, 0, bc.nextAbsentValue(uint16(0))) - assert.Equal(t, 5, bc.nextAbsentValue(uint16(5))) - assert.Equal(t, 11, bc.nextAbsentValue(uint16(11))) - assert.Equal(t, 14, bc.nextAbsentValue(uint16(12))) - assert.Equal(t, 14, bc.nextAbsentValue(uint16(13))) - assert.Equal(t, 14, bc.nextAbsentValue(uint16(14))) - assert.Equal(t, 49, bc.nextAbsentValue(uint16(49))) - assert.Equal(t, 60, bc.nextAbsentValue(uint16(50))) - assert.Equal(t, 60, bc.nextAbsentValue(uint16(60))) - assert.Equal(t, 101, bc.nextAbsentValue(uint16(100))) - assert.Equal(t, 101, bc.nextAbsentValue(uint16(101))) - assert.Equal(t, 130, bc.nextAbsentValue(uint16(129))) - - assert.Equal(t, 0, bc.previousAbsentValue(uint16(0))) - assert.Equal(t, 1, bc.previousAbsentValue(uint16(1))) - assert.Equal(t, 2, bc.previousAbsentValue(uint16(2))) - assert.Equal(t, 5, bc.previousAbsentValue(uint16(5))) - assert.Equal(t, 9, bc.previousAbsentValue(uint16(10))) - assert.Equal(t, 11, bc.previousAbsentValue(uint16(11))) - assert.Equal(t, 11, bc.previousAbsentValue(uint16(12))) - assert.Equal(t, 11, bc.previousAbsentValue(uint16(13))) - assert.Equal(t, 49, bc.previousAbsentValue(uint16(50))) - assert.Equal(t, 49, bc.previousAbsentValue(uint16(51))) - assert.Equal(t, 99, bc.previousAbsentValue(uint16(100))) + t.Run("Next value", func(t *testing.T) { + assert.Equal(t, 10, bc.nextValue(uint16(0))) + assert.Equal(t, 10, bc.nextValue(uint16(5))) + assert.Equal(t, 10, bc.nextValue(uint16(10))) + assert.Equal(t, 12, bc.nextValue(uint16(11))) + assert.Equal(t, 12, bc.nextValue(uint16(12))) + assert.Equal(t, 13, bc.nextValue(uint16(13))) + assert.Equal(t, 50, bc.nextValue(uint16(14))) + assert.Equal(t, 55, bc.nextValue(uint16(55))) + assert.Equal(t, 100, bc.nextValue(uint16(61))) + assert.Equal(t, 100, bc.nextValue(uint16(100))) + assert.Equal(t, 129, bc.nextValue(uint16(101))) + assert.Equal(t, 129, bc.nextValue(uint16(129))) + assert.Equal(t, -1, bc.nextValue(uint16(130))) + }) + + t.Run("Previous value", func(t *testing.T) { + assert.Equal(t, -1, bc.previousValue(uint16(0))) + assert.Equal(t, -1, bc.previousValue(uint16(1))) + assert.Equal(t, -1, bc.previousValue(uint16(2))) + assert.Equal(t, -1, bc.previousValue(uint16(5))) + assert.Equal(t, 10, bc.previousValue(uint16(10))) + assert.Equal(t, 10, bc.previousValue(uint16(11))) + assert.Equal(t, 12, bc.previousValue(uint16(12))) + assert.Equal(t, 13, bc.previousValue(uint16(13))) + assert.Equal(t, 13, bc.previousValue(uint16(14))) + assert.Equal(t, 55, bc.previousValue(uint16(55))) + assert.Equal(t, 59, bc.previousValue(uint16(61))) + assert.Equal(t, 100, bc.previousValue(uint16(101))) + }) + + t.Run("Next Absent value", func(t *testing.T) { + assert.Equal(t, 0, bc.nextAbsentValue(uint16(0))) + assert.Equal(t, 5, bc.nextAbsentValue(uint16(5))) + assert.Equal(t, 11, bc.nextAbsentValue(uint16(11))) + assert.Equal(t, 14, bc.nextAbsentValue(uint16(12))) + assert.Equal(t, 14, bc.nextAbsentValue(uint16(13))) + assert.Equal(t, 14, bc.nextAbsentValue(uint16(14))) + assert.Equal(t, 49, bc.nextAbsentValue(uint16(49))) + assert.Equal(t, 60, bc.nextAbsentValue(uint16(50))) + assert.Equal(t, 60, bc.nextAbsentValue(uint16(60))) + assert.Equal(t, 101, bc.nextAbsentValue(uint16(100))) + assert.Equal(t, 101, bc.nextAbsentValue(uint16(101))) + assert.Equal(t, 130, bc.nextAbsentValue(uint16(129))) + }) + + t.Run("Previous Absent value", func(t *testing.T) { + assert.Equal(t, 0, bc.previousAbsentValue(uint16(0))) + assert.Equal(t, 1, bc.previousAbsentValue(uint16(1))) + assert.Equal(t, 2, bc.previousAbsentValue(uint16(2))) + assert.Equal(t, 5, bc.previousAbsentValue(uint16(5))) + assert.Equal(t, 9, bc.previousAbsentValue(uint16(10))) + assert.Equal(t, 11, bc.previousAbsentValue(uint16(11))) + assert.Equal(t, 11, bc.previousAbsentValue(uint16(12))) + assert.Equal(t, 11, bc.previousAbsentValue(uint16(13))) + assert.Equal(t, 49, bc.previousAbsentValue(uint16(50))) + assert.Equal(t, 49, bc.previousAbsentValue(uint16(51))) + assert.Equal(t, 99, bc.previousAbsentValue(uint16(100))) + assert.Equal(t, 128, bc.previousAbsentValue(uint16(129))) + assert.Equal(t, 130, bc.previousAbsentValue(uint16(130))) + }) +} + +func TestNextAbsent(t *testing.T) { + bc := newBitmapContainer() + for i := 0; i < 1<<16; i++ { + bc.iadd(uint16(i)) + } + v := bc.nextAbsentValue((1 << 16) - 1) + assert.Equal(t, v, 65536) } func TestBitMapContainerValidate(t *testing.T) { diff --git a/roaring.go b/roaring.go index 9508711a..769bfea5 100644 --- a/roaring.go +++ b/roaring.go @@ -1878,6 +1878,131 @@ func (rb *Bitmap) CloneCopyOnWriteContainers() { rb.highlowcontainer.cloneCopyOnWriteContainers() } +func (rb *Bitmap) NextValue(target int) int { + originalKey := highbits(uint32(target)) + query := lowbits(uint32(target)) + nextValue := -1 + containerIndex := rb.highlowcontainer.advanceUntil(originalKey, -1) + for containerIndex < rb.highlowcontainer.size() && nextValue == -1 { + containerKey := rb.highlowcontainer.getKeyAtIndex(containerIndex) + container := rb.highlowcontainer.getContainer(containerKey) + // if containerKey > orginalKey then we are past the container which mapped to the orignal key + // in that case we can just return the minimum from that container + var responseBit int + if containerKey > originalKey { + bit, err := container.safeMinimum() + if err == nil { + responseBit = -1 + } + responseBit = int(bit) + } else { + responseBit = container.nextValue(query) + } + + if responseBit == -1 { + nextValue = -1 + } else { + nextValue = int(combineLoHi32(uint32(responseBit), uint32(containerKey))) + } + containerIndex++ + } + + return nextValue +} + +func (rb *Bitmap) PreviousValue(target int) int { + if rb.IsEmpty() { + return -1 + } + + originalKey := highbits(uint32(target)) + query := lowbits(uint32(target)) + prevValue := -1 + containerIndex := rb.highlowcontainer.advanceUntil(originalKey, -1) + + if containerIndex == rb.highlowcontainer.size() { + return int(rb.Maximum()) + } + + if rb.highlowcontainer.getKeyAtIndex(containerIndex) > originalKey { + return -1 + } + + for containerIndex != -1 && prevValue == -1 { + containerKey := rb.highlowcontainer.getKeyAtIndex(containerIndex) + container := rb.highlowcontainer.getContainer(containerKey) + // if containerKey > orginalKey then we are past the container which mapped to the orignal key + // in that case we can just return the minimum from that container + var responseBit int + if containerKey < originalKey { + bit, err := container.safeMaximum() + + if err == nil { + responseBit = -1 + } + responseBit = int(bit) + } else { + responseBit = container.previousValue(query) + } + + if responseBit == -1 { + prevValue = -1 + } else { + prevValue = int(combineLoHi32(uint32(responseBit), uint32(containerKey))) + } + containerIndex-- + } + + return prevValue +} + +func (rb *Bitmap) NextAbsentValue(target int) int { + originalKey := highbits(uint32(target)) + query := lowbits(uint32(target)) + nextValue := -1 + + containerIndex := rb.highlowcontainer.advanceUntil(originalKey, -1) + if containerIndex == rb.highlowcontainer.size() { + // if we are here it means no container found, just return the target + return target + } + + containerKey := rb.highlowcontainer.getKeyAtIndex(containerIndex) + + keyspace := uint32(containerKey) << 16 + if target < int(keyspace) { + // target is less than the start of the keyspace start + // that means target cannot be in the keyspace + return target + } + + container := rb.highlowcontainer.getContainer(containerKey) + nextValue = container.nextAbsentValue(query) + for { + if nextValue != (1 << 16) { + return int(combineLoHi32(uint32(nextValue), keyspace)) + } + + if containerIndex == rb.highlowcontainer.size()-1 { + val, err := container.safeMaximum() + if err == nil { + return -1 + } + return int(val) + 1 + } + containerIndex++ + nextContainerKey := rb.highlowcontainer.getKeyAtIndex(containerIndex) + if containerKey < nextContainerKey { + // There is a gap between keys + // Just increment the current key and shift to get HoB + return int(containerKey+1) << 16 + } + containerKey = nextContainerKey + container = rb.highlowcontainer.getContainer(containerKey) + nextValue = container.nextAbsentValue(0) + } +} + // FlipInt calls Flip after casting the parameters (convenience method) func FlipInt(bm *Bitmap, rangeStart, rangeEnd int) *Bitmap { return Flip(bm, uint64(rangeStart), uint64(rangeEnd)) diff --git a/roaring_test.go b/roaring_test.go index 7124dfd6..4e89b06d 100644 --- a/roaring_test.go +++ b/roaring_test.go @@ -2773,6 +2773,155 @@ func TestBitMapValidationFromDeserialization(t *testing.T) { } } +func TestNextAndPreviousValue(t *testing.T) { + t.Run("Java Regression1 ", func(t *testing.T) { + // [Java1] https://github.com/RoaringBitmap/RoaringBitmap/blob/5235aa62c32fa3bf7fae40a562e3edc75f61be4e/RoaringBitmap/src/test/java/org/roaringbitmap/TestRunContainer.java#L3645 + bmp := New() + bmp.AddRange(64, 129) + assert.Equal(t, 64, bmp.NextValue(64)) + assert.Equal(t, 64, bmp.NextValue(0)) + assert.Equal(t, 64, bmp.NextValue(64)) + assert.Equal(t, 65, bmp.NextValue(65)) + assert.Equal(t, 128, bmp.NextValue(128)) + assert.Equal(t, -1, bmp.NextValue(129)) + + assert.Equal(t, -1, bmp.PreviousValue(0)) + assert.Equal(t, -1, bmp.PreviousValue(63)) + assert.Equal(t, 64, bmp.PreviousValue(64)) + assert.Equal(t, 65, bmp.PreviousValue(65)) + assert.Equal(t, 128, bmp.PreviousValue(128)) + assert.Equal(t, 128, bmp.PreviousValue(129)) + + assert.Equal(t, 0, bmp.NextAbsentValue(0)) + assert.Equal(t, 63, bmp.NextAbsentValue(63)) + assert.Equal(t, 129, bmp.NextAbsentValue(64)) + assert.Equal(t, 129, bmp.NextAbsentValue(65)) + assert.Equal(t, 129, bmp.NextAbsentValue(128)) + assert.Equal(t, 129, bmp.NextAbsentValue(129)) + }) + t.Run("Java Regression2", func(t *testing.T) { + // [Java2] https://github.com/RoaringBitmap/RoaringBitmap/blob/5235aa62c32fa3bf7fae40a562e3edc75f61be4e/RoaringBitmap/src/test/java/org/roaringbitmap/TestRunContainer.java#L3655 + + bmp := New() + bmp.AddRange(64, 129) + bmp.AddRange(256, 256+64+1) + assert.Equal(t, 64, bmp.NextValue(0)) + assert.Equal(t, 64, bmp.NextValue(64)) + assert.Equal(t, 65, bmp.NextValue(65)) + assert.Equal(t, 128, bmp.NextValue(128)) + assert.Equal(t, 256, bmp.NextValue(129)) + assert.Equal(t, -1, bmp.NextValue(512)) + + assert.Equal(t, -1, bmp.PreviousValue(0)) + assert.Equal(t, -1, bmp.PreviousValue(63)) + assert.Equal(t, 64, bmp.PreviousValue(64)) + assert.Equal(t, 65, bmp.PreviousValue(65)) + assert.Equal(t, 128, bmp.PreviousValue(128)) + assert.Equal(t, 128, bmp.PreviousValue(129)) + assert.Equal(t, 128, bmp.PreviousValue(199)) + assert.Equal(t, 128, bmp.PreviousValue(200)) + assert.Equal(t, 128, bmp.PreviousValue(250)) + assert.Equal(t, 256, bmp.PreviousValue(256)) + assert.Equal(t, 320, bmp.PreviousValue(2500)) + + assert.Equal(t, 0, bmp.NextAbsentValue(0)) + assert.Equal(t, 63, bmp.NextAbsentValue(63)) + assert.Equal(t, 129, bmp.NextAbsentValue(64)) + assert.Equal(t, 129, bmp.NextAbsentValue(65)) + assert.Equal(t, 129, bmp.NextAbsentValue(128)) + assert.Equal(t, 129, bmp.NextAbsentValue(129)) + assert.Equal(t, 199, bmp.NextAbsentValue(199)) + assert.Equal(t, 200, bmp.NextAbsentValue(200)) + assert.Equal(t, 250, bmp.NextAbsentValue(250)) + assert.Equal(t, 321, bmp.NextAbsentValue(256)) + assert.Equal(t, 321, bmp.NextAbsentValue(320)) + }) + + t.Run("Java Regression3", func(t *testing.T) { + // [Java3] https://github.com/RoaringBitmap/RoaringBitmap/blob/5235aa62c32fa3bf7fae40a562e3edc75f61be4e/RoaringBitmap/src/test/java/org/roaringbitmap/TestRunContainer.java#L3666 + + bmp := New() + bmp.AddRange(64, 129) + bmp.AddRange(200, 200+300+1) + bmp.AddRange(5000, 5000+200+1) + assert.Equal(t, 64, bmp.NextValue(0)) + assert.Equal(t, 64, bmp.NextValue(63)) + assert.Equal(t, 64, bmp.NextValue(64)) + assert.Equal(t, 65, bmp.NextValue(65)) + assert.Equal(t, 128, bmp.NextValue(128)) + assert.Equal(t, 200, bmp.NextValue(129)) + assert.Equal(t, 200, bmp.NextValue(199)) + assert.Equal(t, 200, bmp.NextValue(200)) + assert.Equal(t, 250, bmp.NextValue(250)) + assert.Equal(t, 5000, bmp.NextValue(2500)) + assert.Equal(t, 5000, bmp.NextValue(5000)) + assert.Equal(t, 5200, bmp.NextValue(5200)) + assert.Equal(t, -1, bmp.NextValue(5201)) + + assert.Equal(t, -1, bmp.PreviousValue(0)) + assert.Equal(t, -1, bmp.PreviousValue(63)) + assert.Equal(t, 64, bmp.PreviousValue(64)) + assert.Equal(t, 65, bmp.PreviousValue(65)) + assert.Equal(t, 128, bmp.PreviousValue(128)) + assert.Equal(t, 128, bmp.PreviousValue(129)) + assert.Equal(t, 128, bmp.PreviousValue(199)) + assert.Equal(t, 200, bmp.PreviousValue(200)) + assert.Equal(t, 250, bmp.PreviousValue(250)) + assert.Equal(t, 500, bmp.PreviousValue(2500)) + assert.Equal(t, 5000, bmp.PreviousValue(5000)) + assert.Equal(t, 5200, bmp.PreviousValue(5200)) + assert.Equal(t, 5200, bmp.PreviousValue(5201)) + + assert.Equal(t, 0, bmp.NextAbsentValue(0)) + assert.Equal(t, 63, bmp.NextAbsentValue(63)) + assert.Equal(t, 129, bmp.NextAbsentValue(64)) + assert.Equal(t, 129, bmp.NextAbsentValue(65)) + assert.Equal(t, 129, bmp.NextAbsentValue(128)) + assert.Equal(t, 129, bmp.NextAbsentValue(129)) + assert.Equal(t, 199, bmp.NextAbsentValue(199)) + assert.Equal(t, 501, bmp.NextAbsentValue(200)) + assert.Equal(t, 501, bmp.NextAbsentValue(250)) + assert.Equal(t, 2500, bmp.NextAbsentValue(2500)) + assert.Equal(t, 5201, bmp.NextAbsentValue(5000)) + assert.Equal(t, 5201, bmp.NextAbsentValue(5200)) + assert.Equal(t, 5201, bmp.NextAbsentValue(5201)) + }) + + t.Run("skip odd ", func(t *testing.T) { + bmp := New() + for i := 0; i < 2000; i++ { + bmp.Add(uint32(i * 2)) + } + for i := 0; i < 2000; i++ { + assert.Equal(t, i*2, bmp.NextValue(i*2)) + assert.Equal(t, i*2, bmp.PreviousValue(i*2)) + assert.Equal(t, i*2+1, bmp.NextAbsentValue(i*2+1)) + } + }) + + t.Run("skipping with ranges", func(t *testing.T) { + bmp := New() + intervalEnd := 512 + rangeStart := intervalEnd * 2 + rangeEnd := 2048 + for i := 0; i < intervalEnd; i++ { + bmp.Add(uint32(i * 2)) + } + bmp.AddRange(uint64(rangeStart), uint64(rangeEnd)) + + for i := 0; i < intervalEnd; i++ { + assert.Equal(t, i*2, bmp.NextValue(i*2)) + assert.Equal(t, i*2, bmp.PreviousValue(i*2)) + assert.Equal(t, i*2+1, bmp.NextAbsentValue(i*2)) + } + for i := rangeStart; i < rangeEnd; i++ { + assert.Equal(t, i, bmp.NextValue(i)) + assert.Equal(t, i, bmp.PreviousValue(i)) + assert.Equal(t, rangeEnd, bmp.NextAbsentValue(rangeEnd)) + } + }) +} + func BenchmarkFromDense(b *testing.B) { testDense(func(name string, rb *Bitmap) { dense := make([]uint64, rb.DenseSize()) diff --git a/roaringarray.go b/roaringarray.go index bd14bd1c..712e8f18 100644 --- a/roaringarray.go +++ b/roaringarray.go @@ -73,6 +73,8 @@ type container interface { String() string containerType() contype + safeMinimum() (uint16, error) + safeMaximum() (uint16, error) nextValue(x uint16) int previousValue(x uint16) int nextAbsentValue(x uint16) int @@ -298,6 +300,8 @@ func (ra *roaringArray) cloneCopyOnWriteContainers() { // return (ra.binarySearch(0, int64(len(ra.keys)), x) >= 0) //} +// getContainer returns the container with key `x` +// if no such container exists `nil` is returned func (ra *roaringArray) getContainer(x uint16) container { i := ra.binarySearch(0, int64(len(ra.keys)), x) if i < 0 { @@ -345,7 +349,10 @@ func (ra *roaringArray) getWritableContainerAtIndex(i int) container { return ra.containers[i] } +// getIndex returns the index of the container with key `x` +// if no such container exists a negative value is returned func (ra *roaringArray) getIndex(x uint16) int { + // Todo : test // before the binary search, we optimize for frequent cases size := len(ra.keys) if (size == 0) || (ra.keys[size-1] == x) { @@ -405,7 +412,10 @@ func (ra *roaringArray) size() int { return len(ra.keys) } +// binarySearch returns the index of the key. +// negative value returned if not found func (ra *roaringArray) binarySearch(begin, end int64, ikey uint16) int { + // TODO: add unit tests low := begin high := end - 1 for low+16 <= high { @@ -698,6 +708,15 @@ func (ra *roaringArray) hasRunCompression() bool { return false } +/** + * Find the smallest integer index larger than pos such that array[index].key>=x. If none can + * be found, return size. Based on code by O. Kaser. + * + * @param x 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 *roaringArray) advanceUntil(min uint16, pos int) int { lower := pos + 1 diff --git a/runcontainer.go b/runcontainer.go index 87172f9e..645fe737 100644 --- a/runcontainer.go +++ b/runcontainer.go @@ -1798,10 +1798,25 @@ func (rc *runContainer16) minimum() uint16 { return rc.iv[0].start // assume not empty } +func (rc *runContainer16) safeMinimum() (uint16, error) { + if len(rc.iv) == 0 { + return 0, errors.New("Empty runs") + } + + return rc.minimum(), nil +} + func (rc *runContainer16) maximum() uint16 { return rc.iv[len(rc.iv)-1].last() // assume not empty } +func (rc *runContainer16) safeMaximum() (uint16, error) { + if len(rc.iv) == 0 { + return 0, errors.New("Empty runs") + } + return rc.maximum(), nil // assume not empty +} + func (rc *runContainer16) isFull() bool { return (len(rc.iv) == 1) && ((rc.iv[0].start == 0) && (rc.iv[0].last() == MaxUint16)) } @@ -2640,15 +2655,24 @@ func (rc *runContainer16) addOffset(x uint16) (container, container) { // If the target is in the interior or a run then `target` will be returned // Ex: If our run structure resmembles [[a,c], [d,f]] with a <= target <= c then `target` will be returned. // Ex: If c < target < d then d is returned. -// if the target is out of bounds a -1 is returned +// Ex: If target < a then a is returned +// if the target > max, this is out of bounds and -1 is returned func (rc *runContainer16) nextValue(target uint16) int { + if len(rc.iv) == 0 { + return -1 + } + whichIndex, alreadyPresent, _ := rc.search(int(target)) if alreadyPresent { return int(target) } - if whichIndex == -1 || whichIndex == len(rc.iv)-1 { + if whichIndex == -1 { + return int(rc.iv[0].start) + } + + if whichIndex == len(rc.iv)-1 { return -1 } @@ -2666,29 +2690,18 @@ func (rc *runContainer16) nextValue(target uint16) int { // nextAbsentValue returns the next absent value. // By construction the next absent value will be located between gaps in runs -// Ex: if our runs resemble [[a,b],[c,d]] then b+1 will not be equal to c, b+1 will be returned -// if the target is out of bounds a -1 is returned +// +// Ex: if our runs resemble [[a,b],[c,d]] and a <= target <= b then b+1 will not be equal to c, b+1 will be returned +// Ex: if target < a then target is returned +// Ex: if target > d then target is returned func (rc *runContainer16) nextAbsentValue(target uint16) int { whichIndex, alreadyPresent, _ := rc.search(int(target)) - lastIndex := len(rc.iv) - 1 if !alreadyPresent { - if whichIndex == -1 || whichIndex == lastIndex { - // TODO ask about the case whichIndex == len(rc.iv)-1 - // should we return rc.iv[whichIndex].last() + 1 - return -1 - } - return int(target) } - if whichIndex != lastIndex { - // if whichIndex is not the last index, then there is another run with larger start - // rc.iv[whichIndex].last() + 1 cannot equal rc.iv[whichIndex +1].start - // by invariant - return int(rc.iv[whichIndex].last()) + 1 - } - return -1 + return int(rc.iv[whichIndex].last()) + 1 } // previousValue will return the previous present value @@ -2697,14 +2710,19 @@ func (rc *runContainer16) nextAbsentValue(target uint16) int { // Example: // If our run structure resmembles [[a,c], [d,f]] with a <= target <= c then target will be returned. // If c < target < d then c is returned. -// if the target is out of bounds -1 is returned +// if target > f then f is returned +// if the target is less than a, this is out of bounds and -1 is returned func (rc *runContainer16) previousValue(target uint16) int { whichIndex, alreadyPresent, _ := rc.search(int(target)) + if len(rc.iv) == 0 { + return int(target) + } + if alreadyPresent { return int(target) } - if whichIndex == -1 || whichIndex == len(rc.iv)-1 { + if whichIndex == -1 { return -1 } @@ -2716,28 +2734,16 @@ func (rc *runContainer16) previousValue(target uint16) int { // // Example: // If our run structure resmembles [[x,z], [a,c], [d,f]] with a <= target <= c then a-1 will be returned. -// if the target is out of bounds a -1 is returned +// if the target < x then target is returned +// if target > f then target is returned func (rc *runContainer16) previousAbsentValue(target uint16) int { whichIndex, alreadyPresent, _ := rc.search(int(target)) - lastIndex := len(rc.iv) - 1 if !alreadyPresent { - if whichIndex == -1 || whichIndex == lastIndex { - // TODO ask about the case whichIndex == len(rc.iv)-1 - // should we return rc.iv[whichIndex].last() + 1 - return -1 - } - return int(target) } - if whichIndex != 0 { - // if whichIndex is not the last index, then there is another run with larger start - // rc.iv[whichIndex].last() + 1 cannot equal rc.iv[whichIndex +1].start - // by invariant - return int(rc.iv[whichIndex].start) - 1 - } - return -1 + return int(rc.iv[whichIndex].start) - 1 } // isNonContiguousDisjoint returns an error if the intervals overlap e.g have non-empty intersection diff --git a/runcontainer_test.go b/runcontainer_test.go index d870d81e..296c3d6c 100644 --- a/runcontainer_test.go +++ b/runcontainer_test.go @@ -2306,64 +2306,178 @@ func TestAllContainerMethodsAllContainerTypesWithData067(t *testing.T) { }) } -func TestNextPreviousValue(t *testing.T) { - runContainer := newRunContainer16() - runContainer.iaddRange(2, 10) - runContainer.iaddRange(20, 30) - runContainer.iaddRange(31, 40) - runContainer.iaddRange(60, 70) - - assert.Equal(t, 2, runContainer.nextValue(2)) - assert.Equal(t, 5, runContainer.nextValue(5)) - assert.Equal(t, 20, runContainer.nextValue(10)) - assert.Equal(t, 20, runContainer.nextValue(15)) - assert.Equal(t, 20, runContainer.nextValue(20)) - assert.Equal(t, 21, runContainer.nextValue(21)) - assert.Equal(t, 29, runContainer.nextValue(29)) - assert.Equal(t, 31, runContainer.nextValue(30)) - assert.Equal(t, 60, runContainer.nextValue(40)) - assert.Equal(t, 60, runContainer.nextValue(45)) - assert.Equal(t, -1, runContainer.nextValue(80)) - - assert.Equal(t, 2, runContainer.previousValue(2)) - assert.Equal(t, 5, runContainer.previousValue(5)) - assert.Equal(t, 9, runContainer.previousValue(10)) - assert.Equal(t, 9, runContainer.previousValue(15)) - assert.Equal(t, 20, runContainer.previousValue(20)) - assert.Equal(t, 21, runContainer.previousValue(21)) - assert.Equal(t, 29, runContainer.previousValue(29)) - assert.Equal(t, 29, runContainer.previousValue(30)) - assert.Equal(t, 39, runContainer.previousValue(40)) - assert.Equal(t, 39, runContainer.previousValue(45)) - assert.Equal(t, -1, runContainer.previousValue(80)) - - assert.Equal(t, -1, runContainer.nextAbsentValue(0)) - assert.Equal(t, -1, runContainer.nextAbsentValue(1)) - assert.Equal(t, 10, runContainer.nextAbsentValue(5)) - assert.Equal(t, 10, runContainer.nextAbsentValue(10)) - assert.Equal(t, 15, runContainer.nextAbsentValue(15)) - assert.Equal(t, 30, runContainer.nextAbsentValue(20)) - assert.Equal(t, 30, runContainer.nextAbsentValue(21)) - assert.Equal(t, 30, runContainer.nextAbsentValue(29)) - assert.Equal(t, 30, runContainer.nextAbsentValue(30)) - assert.Equal(t, 40, runContainer.nextAbsentValue(31)) - assert.Equal(t, 40, runContainer.nextAbsentValue(40)) - assert.Equal(t, 45, runContainer.nextAbsentValue(45)) - assert.Equal(t, -1, runContainer.nextAbsentValue(80)) - - assert.Equal(t, -1, runContainer.previousAbsentValue(0)) - assert.Equal(t, -1, runContainer.previousAbsentValue(1)) - assert.Equal(t, -1, runContainer.previousAbsentValue(5)) - assert.Equal(t, 10, runContainer.previousAbsentValue(10)) - assert.Equal(t, 15, runContainer.previousAbsentValue(15)) - assert.Equal(t, 19, runContainer.previousAbsentValue(20)) - assert.Equal(t, 19, runContainer.previousAbsentValue(21)) - assert.Equal(t, 19, runContainer.previousAbsentValue(29)) - assert.Equal(t, 30, runContainer.previousAbsentValue(30)) - assert.Equal(t, 30, runContainer.previousAbsentValue(31)) - assert.Equal(t, 40, runContainer.previousAbsentValue(40)) - assert.Equal(t, 45, runContainer.previousAbsentValue(45)) - assert.Equal(t, -1, runContainer.previousAbsentValue(80)) +func TestNextValueRun(t *testing.T) { + t.Run("Java Regression1", func(t *testing.T) { + // [Java1] https://github.com/RoaringBitmap/RoaringBitmap/blob/5235aa62c32fa3bf7fae40a562e3edc75f61be4e/RoaringBitmap/src/test/java/org/roaringbitmap/TestRunContainer.java#L3645 + runContainer := newRunContainer16() + runContainer.iaddRange(64, 129) + assert.Equal(t, 64, runContainer.nextValue(0)) + assert.Equal(t, 64, runContainer.nextValue(64)) + assert.Equal(t, 65, runContainer.nextValue(65)) + assert.Equal(t, 128, runContainer.nextValue(128)) + assert.Equal(t, -1, runContainer.nextValue(129)) + }) + + t.Run("Java Regression2", func(t *testing.T) { + // [Java2] https://github.com/RoaringBitmap/RoaringBitmap/blob/5235aa62c32fa3bf7fae40a562e3edc75f61be4e/RoaringBitmap/src/test/java/org/roaringbitmap/TestRunContainer.java#L3655 + + runContainer := newRunContainer16() + runContainer.iaddRange(64, 129) + runContainer.iaddRange(256, 256+64+1) + assert.Equal(t, 64, runContainer.nextValue(0)) + assert.Equal(t, 64, runContainer.nextValue(64)) + assert.Equal(t, 65, runContainer.nextValue(65)) + assert.Equal(t, 128, runContainer.nextValue(128)) + assert.Equal(t, 256, runContainer.nextValue(129)) + assert.Equal(t, -1, runContainer.nextValue(512)) + }) + + t.Run("Java Regression3", func(t *testing.T) { + // [Java3] https://github.com/RoaringBitmap/RoaringBitmap/blob/5235aa62c32fa3bf7fae40a562e3edc75f61be4e/RoaringBitmap/src/test/java/org/roaringbitmap/TestRunContainer.java#L3666 + + runContainer := newRunContainer16() + runContainer.iaddRange(64, 129) + runContainer.iaddRange(256, 200+300+1) + runContainer.iaddRange(200, 200+300+1) + runContainer.iaddRange(5000, 5000+200+1) + assert.Equal(t, 64, runContainer.nextValue(0)) + assert.Equal(t, 64, runContainer.nextValue(64)) + assert.Equal(t, 64, runContainer.nextValue(64)) + assert.Equal(t, 65, runContainer.nextValue(65)) + assert.Equal(t, 128, runContainer.nextValue(128)) + assert.Equal(t, 200, runContainer.nextValue(129)) + assert.Equal(t, 200, runContainer.nextValue(199)) + assert.Equal(t, 200, runContainer.nextValue(200)) + assert.Equal(t, 250, runContainer.nextValue(250)) + assert.Equal(t, 5000, runContainer.nextValue(2500)) + assert.Equal(t, 5000, runContainer.nextValue(5000)) + assert.Equal(t, 5200, runContainer.nextValue(5200)) + assert.Equal(t, -1, runContainer.nextValue(5201)) + }) +} + +func TestPreviousValueRun(t *testing.T) { + t.Run("Java Regression1", func(t *testing.T) { + // [Java 1] https://github.com/RoaringBitmap/RoaringBitmap/blob/5235aa62c32fa3bf7fae40a562e3edc75f61be4e/RoaringBitmap/src/test/java/org/roaringbitmap/TestRunContainer.java#L3684 + runContainer := newRunContainer16() + runContainer.iaddRange(64, 129) + assert.Equal(t, -1, runContainer.previousValue(0)) + assert.Equal(t, -1, runContainer.previousValue(63)) + assert.Equal(t, 64, runContainer.previousValue(64)) + assert.Equal(t, 65, runContainer.previousValue(65)) + assert.Equal(t, 128, runContainer.previousValue(128)) + assert.Equal(t, 128, runContainer.previousValue(129)) + }) + + t.Run("Java Regression2", func(t *testing.T) { + // [Java 2] https://github.com/RoaringBitmap/RoaringBitmap/blob/5235aa62c32fa3bf7fae40a562e3edc75f61be4e/RoaringBitmap/src/test/java/org/roaringbitmap/TestRunContainer.java#L3695 + runContainer := newRunContainer16() + runContainer.iaddRange(64, 129) + runContainer.iaddRange(200, 200+300+1) + runContainer.iaddRange(5000, 5000+200+1) + assert.Equal(t, -1, runContainer.previousValue(0)) + assert.Equal(t, -1, runContainer.previousValue(63)) + assert.Equal(t, 64, runContainer.previousValue(64)) + assert.Equal(t, 65, runContainer.previousValue(65)) + assert.Equal(t, 128, runContainer.previousValue(128)) + assert.Equal(t, 128, runContainer.previousValue(129)) + assert.Equal(t, 128, runContainer.previousValue(199)) + assert.Equal(t, 200, runContainer.previousValue(200)) + assert.Equal(t, 250, runContainer.previousValue(250)) + assert.Equal(t, 500, runContainer.previousValue(2500)) + assert.Equal(t, 5000, runContainer.previousValue(5000)) + assert.Equal(t, 5200, runContainer.previousValue(5200)) + // TODO Question + assert.Equal(t, 5200, runContainer.previousValue(5201)) + }) +} + +func TestNextAbsentValueRun(t *testing.T) { + t.Run("Java Regression1", func(t *testing.T) { + // [Java 1] https://github.com/RoaringBitmap/RoaringBitmap/blob/5235aa62c32fa3bf7fae40a562e3edc75f61be4e/RoaringBitmap/src/test/java/org/roaringbitmap/TestRunContainer.java#L3760 + runContainer := newRunContainer16() + runContainer.iaddRange(64, 129) + assert.Equal(t, 0, runContainer.nextAbsentValue(0)) + assert.Equal(t, 63, runContainer.nextAbsentValue(63)) + assert.Equal(t, 129, runContainer.nextAbsentValue(64)) + assert.Equal(t, 129, runContainer.nextAbsentValue(65)) + assert.Equal(t, 129, runContainer.nextAbsentValue(128)) + assert.Equal(t, 129, runContainer.nextAbsentValue(129)) + }) + + t.Run("Java Regression2", func(t *testing.T) { + // [Java 2] https://github.com/RoaringBitmap/RoaringBitmap/blob/5235aa62c32fa3bf7fae40a562e3edc75f61be4e/RoaringBitmap/src/test/java/org/roaringbitmap/TestRunContainer.java#L3815 + runContainer := newRunContainer16() + runContainer.iaddRange(64, 129) + runContainer.iaddRange(200, 501) + runContainer.iaddRange(5000, 5201) + + assert.Equal(t, 0, runContainer.nextAbsentValue(0)) + assert.Equal(t, 63, runContainer.nextAbsentValue(63)) + assert.Equal(t, 129, runContainer.nextAbsentValue(64)) + assert.Equal(t, 129, runContainer.nextAbsentValue(65)) + assert.Equal(t, 129, runContainer.nextAbsentValue(128)) + assert.Equal(t, 129, runContainer.nextAbsentValue(129)) + assert.Equal(t, 199, runContainer.nextAbsentValue(199)) + assert.Equal(t, 501, runContainer.nextAbsentValue(200)) + assert.Equal(t, 501, runContainer.nextAbsentValue(250)) + assert.Equal(t, 2500, runContainer.nextAbsentValue(2500)) + assert.Equal(t, 5201, runContainer.nextAbsentValue(5000)) + assert.Equal(t, 5201, runContainer.nextAbsentValue(5200)) + }) + + t.Run("Java Regression3", func(t *testing.T) { + // [Java 3] https://github.com/RoaringBitmap/RoaringBitmap/blob/5235aa62c32fa3bf7fae40a562e3edc75f61be4e/RoaringBitmap/src/test/java/org/roaringbitmap/TestRunContainer.java#L3832 + runContainer := newRunContainer16() + for i := 0; i < 1000; i++ { + assert.Equal(t, i, runContainer.nextAbsentValue(uint16(i))) + } + }) +} + +func TestPreviousAbsentValueRun(t *testing.T) { + t.Run("Java Regression 1", func(t *testing.T) { + // [Java 1] https://github.com/RoaringBitmap/RoaringBitmap/blob/5235aa62c32fa3bf7fae40a562e3edc75f61be4e/RoaringBitmap/src/test/java/org/roaringbitmap/TestRunContainer.java#L3732 + runContainer := newRunContainer16() + runContainer.iaddRange(64, 129) + + assert.Equal(t, 0, runContainer.previousAbsentValue(0)) + assert.Equal(t, 63, runContainer.previousAbsentValue(63)) + assert.Equal(t, 63, runContainer.previousAbsentValue(64)) + assert.Equal(t, 63, runContainer.previousAbsentValue(65)) + assert.Equal(t, 63, runContainer.previousAbsentValue(128)) + assert.Equal(t, 129, runContainer.previousAbsentValue(129)) + }) + + t.Run("Java Regression2", func(t *testing.T) { + // [Java 2] https://github.com/RoaringBitmap/RoaringBitmap/blob/5235aa62c32fa3bf7fae40a562e3edc75f61be4e/RoaringBitmap/src/test/java/org/roaringbitmap/TestRunContainer.java#L3743 + + runContainer := newRunContainer16() + runContainer.iaddRange(64, 129) + runContainer.iaddRange(200, 501) + runContainer.iaddRange(5000, 5201) + + assert.Equal(t, 0, runContainer.previousAbsentValue(0)) + assert.Equal(t, 63, runContainer.previousAbsentValue(63)) + assert.Equal(t, 63, runContainer.previousAbsentValue(64)) + assert.Equal(t, 63, runContainer.previousAbsentValue(65)) + assert.Equal(t, 63, runContainer.previousAbsentValue(128)) + assert.Equal(t, 129, runContainer.previousAbsentValue(129)) + assert.Equal(t, 199, runContainer.previousAbsentValue(199)) + assert.Equal(t, 199, runContainer.previousAbsentValue(200)) + assert.Equal(t, 199, runContainer.previousAbsentValue(250)) + assert.Equal(t, 2500, runContainer.previousAbsentValue(2500)) + assert.Equal(t, 4999, runContainer.previousAbsentValue(5000)) + assert.Equal(t, 4999, runContainer.previousAbsentValue(5200)) + }) + + t.Run("Java Regression3", func(t *testing.T) { + // [Java 3] https://github.com/RoaringBitmap/RoaringBitmap/blob/5235aa62c32fa3bf7fae40a562e3edc75f61be4e/RoaringBitmap/src/test/java/org/roaringbitmap/TestRunContainer.java#L3760 + runContainer := newRunContainer16() + for i := 0; i < 1000; i++ { + assert.Equal(t, i, runContainer.previousAbsentValue(uint16(i))) + } + }) } func TestRuntimeIteratorPeekNext(t *testing.T) { diff --git a/setutil.go b/setutil.go index 7dbd37de..29ba4c3a 100644 --- a/setutil.go +++ b/setutil.go @@ -546,16 +546,6 @@ func binarySearch(array []uint16, ikey uint16) int { return -(low + 1) } -func closestByIndex(array []uint16, smallerIdx int, largerIdx int, target uint16) int { - smallerVal := array[smallerIdx] - largerVal := array[largerIdx] - - if (int(largerVal) - int(target)) >= (int(target) - int(smallerVal)) { - return smallerIdx - } - return largerIdx -} - // searchResult provides information about a search request. // The values will depend on the context of the search type searchResult struct { @@ -600,7 +590,7 @@ func binarySearchUntilWithBounds(array []uint16, target uint16, lowIndex int, ma } if target > array[maxIndex] { - return searchResult{0, closestIndex, false} + return searchResult{0, len(array), false} } for lowIndex <= highIndex { @@ -654,7 +644,7 @@ func binarySearchPastWithBounds(array []uint16, target uint16, lowIndex int, max } if target > array[maxIndex] { - return searchResult{0, closestIndex, false} + return searchResult{0, len(array), false} } for lowIndex <= highIndex { diff --git a/setutil_test.go b/setutil_test.go index 0e5c7a73..1d60e094 100644 --- a/setutil_test.go +++ b/setutil_test.go @@ -182,7 +182,7 @@ func TestBinarySearchUntil(t *testing.T) { { "out of bounds at the end", []uint16{0, 1, 2, 3, 4, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17}, - 100, 0, false, -1, + 100, 0, false, 15, }, { "missing alternating", @@ -217,7 +217,7 @@ func TestBinarySearchPastWithBounds(t *testing.T) { { "has match but not in range", []uint16{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 13, 14, 15, 16, 17}, - 9, 0, false, -1, 0, 4, + 9, 0, false, 15, 0, 4, }, { "matches", @@ -232,7 +232,7 @@ func TestBinarySearchPastWithBounds(t *testing.T) { { "has match but not in range", []uint16{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 13, 14, 15, 16, 17}, - 9, 0, false, -1, 0, 4, + 9, 0, false, 15, 0, 4, }, { "missing 12 with gap", @@ -247,7 +247,7 @@ func TestBinarySearchPastWithBounds(t *testing.T) { { "missing 10 out of range", []uint16{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12}, - 10, 0, false, -1, 0, 5, + 10, 0, false, 12, 0, 5, }, } diff --git a/util.go b/util.go index 48b9d5a1..f58a86b2 100644 --- a/util.go +++ b/util.go @@ -52,6 +52,7 @@ func fill(arr []uint64, val uint64) { arr[i] = val } } + func fillRange(arr []uint64, start, end int, val uint64) { for i := start; i < end; i++ { arr[i] = val @@ -112,10 +113,19 @@ func fillArrayXOR(container []uint16, bitmap1, bitmap2 []uint64) { func highbits(x uint32) uint16 { return uint16(x >> 16) } + func lowbits(x uint32) uint16 { return uint16(x & maxLowBit) } +func combineLoHi16(lob uint16, hob uint16) uint32 { + return combineLoHi32(uint32(lob), uint32(hob)) +} + +func combineLoHi32(lob uint32, hob uint32) uint32 { + return uint32(lob) | (hob << 16) +} + const maxLowBit = 0xFFFF func flipBitmapRange(bitmap []uint64, start int, end int) { @@ -146,7 +156,6 @@ func resetBitmapRange(bitmap []uint64, start int, end int) { bitmap[i] = 0 } bitmap[endword] &= ^(^uint64(0) >> (uint(-end) % 64)) - } func setBitmapRange(bitmap []uint64, start int, end int) { @@ -242,7 +251,6 @@ func selectBitPosition(w uint64, j int) int { } } return seen + int(counter) - } func panicOn(err error) { diff --git a/util_test.go b/util_test.go new file mode 100644 index 00000000..3b6aa0a8 --- /dev/null +++ b/util_test.go @@ -0,0 +1,25 @@ +package roaring + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestLobHob(t *testing.T) { + for i := 0; i < 2049; i++ { + val := uint32(i) + lob := lowbits(uint32(val)) + hob := highbits(uint32(val)) + reconstructed := combineLoHi16(lob, hob) + assert.Equal(t, reconstructed, val) + } + + for i := 0; i < 2049; i++ { + val := uint32(i) + lob := lowbits(uint32(val)) + hob := highbits(uint32(val)) + reconstructed := combineLoHi32(uint32(lob), uint32(hob)) + assert.Equal(t, reconstructed, val) + } +}