From b429bc3140fa38fdb27b86d136e618e03b6f4abd Mon Sep 17 00:00:00 2001 From: Roger Peppe Date: Fri, 7 Nov 2025 16:27:02 +0000 Subject: [PATCH] use open range for UnsetIterator This makes UnsetIterator consistent with all the other methods that take range arguments. Signed-off-by: Roger Peppe --- iter.go | 2 +- iter_test.go | 16 ++++++++-------- roaring.go | 43 ++++++++++++++++++++++++------------------- 3 files changed, 33 insertions(+), 28 deletions(-) diff --git a/iter.go b/iter.go index 0e2d68fb..5b304b7a 100644 --- a/iter.go +++ b/iter.go @@ -32,7 +32,7 @@ func Backward(b *Bitmap) func(func(uint32) bool) { // The iterator becomes invalid if the bitmap is modified (e.g., with Add or Remove). func Unset(b *Bitmap, min, max uint32) func(func(uint32) bool) { return func(yield func(uint32) bool) { - it := b.UnsetIterator(min, max) + it := b.UnsetIterator(uint64(min), uint64(max)+1) for it.HasNext() { if !yield(it.Next()) { return diff --git a/iter_test.go b/iter_test.go index d2b03574..2eae1d38 100644 --- a/iter_test.go +++ b/iter_test.go @@ -315,7 +315,7 @@ func TestUnsetIteratorPeekable(t *testing.T) { b.AddInt(5) b.AddInt(8) - it := b.UnsetIterator(3, 10) + it := b.UnsetIterator(3, 11) // First value should be 3 assert.True(t, it.HasNext()) @@ -357,7 +357,7 @@ func TestUnsetIteratorPeekable(t *testing.T) { b.AddInt(8) b.AddInt(12) - it := b.UnsetIterator(1, 15) + it := b.UnsetIterator(1, 16) // Skip to values >= 7 it.AdvanceIfNeeded(7) @@ -394,7 +394,7 @@ func TestUnsetIteratorPeekable(t *testing.T) { b := New() b.AddInt(5) - it := b.UnsetIterator(10, 15) + it := b.UnsetIterator(10, 16) // Try to advance to a value before our range start it.AdvanceIfNeeded(5) @@ -408,7 +408,7 @@ func TestUnsetIteratorPeekable(t *testing.T) { b := New() b.AddInt(5) - it := b.UnsetIterator(10, 15) + it := b.UnsetIterator(10, 16) // Advance beyond our range it.AdvanceIfNeeded(20) @@ -420,7 +420,7 @@ func TestUnsetIteratorPeekable(t *testing.T) { t.Run("advance if needed on current value", func(t *testing.T) { b := New() b.AddRange(0, 0x10000) - iter := b.UnsetIterator(0, 0x10002) + iter := b.UnsetIterator(0, 0x10003) var got []uint32 prev := uint32(0) for len(got) < 10 { @@ -439,7 +439,7 @@ func TestUnsetIteratorPeekable(t *testing.T) { b := New() b.AddInt(5) // Set bit in middle of range - it := b.UnsetIterator(5, 5) // Range contains only the set bit + it := b.UnsetIterator(5, 6) // Range contains only the set bit // Should have no values assert.False(t, it.HasNext()) @@ -454,7 +454,7 @@ func TestUnsetIteratorPeekable(t *testing.T) { b := New() b.Add(4294967294) // Set the value before max - it := b.UnsetIterator(4294967294, 4294967295) + it := b.UnsetIterator(4294967294, 4294967296) // Should have 4294967295 (max uint32) as it's unset assert.True(t, it.HasNext()) @@ -469,7 +469,7 @@ func TestUnsetIteratorPeekable(t *testing.T) { b := New() b.Add(4294967295) // Set max uint32 - it := b.UnsetIterator(4294967294, 4294967295) + it := b.UnsetIterator(4294967294, 4294967296) // Should have 4294967294 as it's unset, but not 4294967295 assert.True(t, it.HasNext()) diff --git a/roaring.go b/roaring.go index 4963ce19..4327c678 100644 --- a/roaring.go +++ b/roaring.go @@ -743,20 +743,25 @@ func (ii *manyIntIterator) Initialize(a *Bitmap) { } type unsetIterator struct { - min, max uint32 - current uint64 // use uint64 to avoid overflow - it IntPeekable - hasNext bool + start, end uint64 + current uint64 + it IntPeekable + hasNext bool } -// Initialize configures the unset iterator to iterate over values in [min, max] that are not in the bitmap -func (ui *unsetIterator) Initialize(b *Bitmap, min, max uint32) { - ui.min = min - ui.max = max - ui.current = uint64(min) +// Initialize configures the unset iterator to iterate over values in [start, end) that are not in the bitmap +func (ui *unsetIterator) Initialize(b *Bitmap, start, end uint64) { + if end > 0x100000000 { + panic("end > 0x100000000") + } + ui.start = start + ui.end = end + ui.current = start ui.it = b.Iterator() - // Advance to first value >= min - ui.it.AdvanceIfNeeded(min) + // Advance to first value >= start + if start <= MaxUint32 { + ui.it.AdvanceIfNeeded(uint32(start)) + } ui.updateHasNext() } @@ -776,7 +781,7 @@ func (ui *unsetIterator) Next() uint32 { } func (ui *unsetIterator) updateHasNext() { - for ui.current <= uint64(ui.max) { + for ui.current < ui.end { if !ui.it.HasNext() { // No more set bits, we have values to yield ui.hasNext = true @@ -784,8 +789,8 @@ func (ui *unsetIterator) updateHasNext() { } nextSet := ui.it.PeekNext() - if nextSet > ui.max { - // Next set bit is beyond our range, we have values to yield + if uint64(nextSet) >= ui.end { + // Next set bit is at or beyond our range, we have values to yield ui.hasNext = true return } @@ -818,8 +823,8 @@ func (ui *unsetIterator) AdvanceIfNeeded(minval uint32) { return // Already at or past minval } - if minval > ui.max { - // Beyond our range, no more values + if uint64(minval) >= ui.end { + // At or beyond our range, no more values ui.hasNext = false return } @@ -915,11 +920,11 @@ func (rb *Bitmap) ManyIterator() ManyIntIterable { return p } -// UnsetIterator creates a new IntPeekable to iterate over values in the range [min, max] that are NOT contained in the bitmap. +// UnsetIterator creates a new IntPeekable to iterate over values in the range [start, end) that are NOT contained in the bitmap. // The iterator becomes invalid if the bitmap is modified (e.g., with Add or Remove). -func (rb *Bitmap) UnsetIterator(min, max uint32) IntPeekable { +func (rb *Bitmap) UnsetIterator(start, end uint64) IntPeekable { p := new(unsetIterator) - p.Initialize(rb, min, max) + p.Initialize(rb, start, end) return p }