Skip to content

Commit

Permalink
Merge pull request #424 from bearrito/feature/previous-next-values
Browse files Browse the repository at this point in the history
Feature/previous next values
  • Loading branch information
lemire authored Jun 11, 2024
2 parents b729426 + 761272e commit 612b5f6
Show file tree
Hide file tree
Showing 15 changed files with 1,677 additions and 28 deletions.
189 changes: 189 additions & 0 deletions arraycontainer.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -969,6 +985,179 @@ func (ac *arrayContainer) realloc(size int) {
}
}

// previousValue returns either the target if found or the previous smaller present value.
// If the target is out of bounds a -1 is returned.
// 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 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 8
func (ac *arrayContainer) previousAbsentValue(target uint16) int {
cardinality := len(ac.content)

if cardinality == 0 {
return int(target)
}

if target > ac.maximum() {
return int(target)
}

result := binarySearchPast(ac.content, target)

if result.notFound() {
return int(target)
}

// If the target was found at index 1, then the next value down must be result.value-1
if result.index == 1 {
if ac.minimum() != result.value-1 {
return int(result.value - 1)
}
}

low := -1
high := result.index

// This uses the pigeon-hole principle.
// the if statement compares the difference in indices vs
// the difference in values. Suppose mid = 10 and result.index = 5
// with ac.content[mid] = 100 and target = 10
// then we have 5 slots for values but we need to fit in 90 values
// so some of the values must be missing
for low+1 < high {
midIndex := (high + low) >> 1
indexDifference := result.index - midIndex
valueDifference := target - ac.content[midIndex]
if indexDifference < int(valueDifference) {
low = midIndex
} else {
high = midIndex
}
}

if high == 0 {
return int(ac.minimum()) - 1
}

return int(ac.content[high] - 1)
}

// nextAbsentValue 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 5
// Ex: target=5 ac=[1,2,3,4,6,7] returns 5
// 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 cardinality == 0 {
return int(target)
}
if target < ac.minimum() {
return int(target)
}

result := binarySearchPast(ac.content, target)

if result.notFound() {
return int(target)
}

if result.index == cardinality-2 {
if ac.maximum() != result.value+1 {
return int(result.value + 1)
}
}

low := result.index
high := len(ac.content)

// This uses the pigeon-hole principle.
// the if statement compares the difference in indices vs
// the difference in values. Suppose mid = 10 and result.index = 5
// with ac.content[mid] = 100 and target = 10
// then we have 5 slots for values but we need to fit in 90 values
// so some of the values must be missing
for low+1 < high {
midIndex := (high + low) >> 1
indexDifference := midIndex - result.index
valueDifference := ac.content[midIndex] - target
if indexDifference < int(valueDifference) {
high = midIndex
} else {
low = midIndex
}
}

if low == cardinality-1 {
return int(ac.content[cardinality-1] + 1)
}

return int(ac.content[low] + 1)
}

// 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
// }

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
}

if result.index < len(ac.content)-1 {
return int(ac.content[result.index+1])
}
return -1
}

func newArrayContainer() *arrayContainer {
p := new(arrayContainer)
return p
Expand Down
175 changes: 175 additions & 0 deletions arraycontainer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,181 @@ func TestArrayContainerResetTo(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(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))
})

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("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(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(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)))
}
})
}

func TestArrayContainerValidation(t *testing.T) {
array := newArrayContainer()
upperBound := arrayDefaultMaxSize
Expand Down
Loading

0 comments on commit 612b5f6

Please sign in to comment.