Skip to content

Commit fb89c43

Browse files
committed
fixing issue 22.
1 parent 64d53df commit fb89c43

File tree

3 files changed

+242
-0
lines changed

3 files changed

+242
-0
lines changed

iter.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,16 @@ func Backward(b *Bitmap) func(func(uint32) bool) {
2727
}
2828
}
2929
}
30+
31+
// Unset creates an iterator that yields values in the range [min, max] that are NOT contained in the bitmap.
32+
// The iterator becomes invalid if the bitmap is modified (e.g., with Add or Remove).
33+
func Unset(b *Bitmap, min, max uint32) func(func(uint32) bool) {
34+
return func(yield func(uint32) bool) {
35+
it := b.UnsetIterator(min, max)
36+
for it.HasNext() {
37+
if !yield(it.Next()) {
38+
return
39+
}
40+
}
41+
}
42+
}

iter_test.go

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,3 +119,160 @@ func TestValues(t *testing.T) {
119119

120120
assert.Equal(t, testSize, n)
121121
}
122+
123+
func TestUnset(t *testing.T) {
124+
t.Run("empty bitmap", func(t *testing.T) {
125+
b := New()
126+
it := Unset(b, 5, 10)
127+
128+
expected := []uint32{5, 6, 7, 8, 9, 10}
129+
actual := make([]uint32, 0)
130+
131+
it(func(val uint32) bool {
132+
actual = append(actual, val)
133+
return true
134+
})
135+
136+
assert.Equal(t, expected, actual)
137+
})
138+
139+
t.Run("bitmap with some values set", func(t *testing.T) {
140+
b := New()
141+
b.AddInt(3)
142+
b.AddInt(7)
143+
b.AddInt(12)
144+
145+
it := Unset(b, 5, 10)
146+
147+
expected := []uint32{5, 6, 8, 9, 10}
148+
actual := make([]uint32, 0)
149+
150+
it(func(val uint32) bool {
151+
actual = append(actual, val)
152+
return true
153+
})
154+
155+
assert.Equal(t, expected, actual)
156+
})
157+
158+
t.Run("range completely outside bitmap", func(t *testing.T) {
159+
b := New()
160+
b.AddInt(1)
161+
b.AddInt(2)
162+
b.AddInt(3)
163+
164+
it := Unset(b, 10, 15)
165+
166+
expected := []uint32{10, 11, 12, 13, 14, 15}
167+
actual := make([]uint32, 0)
168+
169+
it(func(val uint32) bool {
170+
actual = append(actual, val)
171+
return true
172+
})
173+
174+
assert.Equal(t, expected, actual)
175+
})
176+
177+
t.Run("range includes set and unset values", func(t *testing.T) {
178+
b := New()
179+
b.AddInt(5)
180+
b.AddInt(8)
181+
b.AddInt(9)
182+
183+
it := Unset(b, 3, 12)
184+
185+
expected := []uint32{3, 4, 6, 7, 10, 11, 12}
186+
actual := make([]uint32, 0)
187+
188+
it(func(val uint32) bool {
189+
actual = append(actual, val)
190+
return true
191+
})
192+
193+
assert.Equal(t, expected, actual)
194+
})
195+
196+
t.Run("min greater than max", func(t *testing.T) {
197+
b := New()
198+
it := Unset(b, 10, 5)
199+
200+
count := 0
201+
it(func(val uint32) bool {
202+
count++
203+
return true
204+
})
205+
206+
assert.Equal(t, 0, count)
207+
})
208+
209+
t.Run("single value range - unset", func(t *testing.T) {
210+
b := New()
211+
b.AddInt(5)
212+
213+
it := Unset(b, 3, 3)
214+
215+
expected := []uint32{3}
216+
actual := make([]uint32, 0)
217+
218+
it(func(val uint32) bool {
219+
actual = append(actual, val)
220+
return true
221+
})
222+
223+
assert.Equal(t, expected, actual)
224+
})
225+
226+
t.Run("single value range - set", func(t *testing.T) {
227+
b := New()
228+
b.AddInt(5)
229+
230+
it := Unset(b, 5, 5)
231+
232+
count := 0
233+
it(func(val uint32) bool {
234+
count++
235+
return true
236+
})
237+
238+
assert.Equal(t, 0, count)
239+
})
240+
241+
t.Run("early termination", func(t *testing.T) {
242+
b := New()
243+
244+
it := Unset(b, 1, 10)
245+
246+
actual := make([]uint32, 0)
247+
it(func(val uint32) bool {
248+
actual = append(actual, val)
249+
return len(actual) < 3 // Stop after 3 values
250+
})
251+
252+
expected := []uint32{1, 2, 3}
253+
assert.Equal(t, expected, actual)
254+
})
255+
256+
t.Run("large range with sparse bitmap", func(t *testing.T) {
257+
b := New()
258+
b.AddInt(100)
259+
b.AddInt(500)
260+
b.AddInt(1000)
261+
262+
it := Unset(b, 50, 150)
263+
264+
actual := make([]uint32, 0)
265+
it(func(val uint32) bool {
266+
actual = append(actual, val)
267+
return true
268+
})
269+
270+
// Should include all values from 50-150 except 100
271+
assert.Equal(t, 100, len(actual)) // 150-50+1 - 1 = 101 - 1 = 100
272+
assert.Contains(t, actual, uint32(50))
273+
assert.Contains(t, actual, uint32(99))
274+
assert.NotContains(t, actual, uint32(100))
275+
assert.Contains(t, actual, uint32(101))
276+
assert.Contains(t, actual, uint32(150))
277+
})
278+
}

roaring.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -742,6 +742,70 @@ func (ii *manyIntIterator) Initialize(a *Bitmap) {
742742
ii.init()
743743
}
744744

745+
type unsetIterator struct {
746+
min, max uint32
747+
current uint32
748+
it IntPeekable
749+
hasNext bool
750+
}
751+
752+
// Initialize configures the unset iterator to iterate over values in [min, max] that are not in the bitmap
753+
func (ui *unsetIterator) Initialize(b *Bitmap, min, max uint32) {
754+
ui.min = min
755+
ui.max = max
756+
ui.current = min
757+
ui.it = b.Iterator()
758+
ui.hasNext = min <= max
759+
760+
// Advance to first value >= min
761+
ui.it.AdvanceIfNeeded(min)
762+
ui.updateHasNext()
763+
}
764+
765+
func (ui *unsetIterator) HasNext() bool {
766+
return ui.hasNext
767+
}
768+
769+
func (ui *unsetIterator) Next() uint32 {
770+
if !ui.hasNext {
771+
panic("Next() called when HasNext() returns false")
772+
}
773+
774+
result := ui.current
775+
ui.current++
776+
ui.updateHasNext()
777+
return result
778+
}
779+
780+
func (ui *unsetIterator) updateHasNext() {
781+
for ui.current <= ui.max {
782+
if !ui.it.HasNext() {
783+
// No more set bits, we have values to yield
784+
ui.hasNext = true
785+
return
786+
}
787+
788+
nextSet := ui.it.PeekNext()
789+
if nextSet > ui.max {
790+
// Next set bit is beyond our range, we have values to yield
791+
ui.hasNext = true
792+
return
793+
}
794+
795+
if ui.current < nextSet {
796+
// We have unset values before the next set bit
797+
ui.hasNext = true
798+
return
799+
}
800+
801+
// Skip the set bit
802+
ui.it.Next()
803+
ui.current = nextSet + 1
804+
}
805+
806+
ui.hasNext = false
807+
}
808+
745809
// String creates a string representation of the Bitmap
746810
func (rb *Bitmap) String() string {
747811
// inspired by https://github.com/fzandona/goroar/
@@ -824,6 +888,14 @@ func (rb *Bitmap) ManyIterator() ManyIntIterable {
824888
return p
825889
}
826890

891+
// UnsetIterator creates a new IntIterable to iterate over values in the range [min, max] that are NOT contained in the bitmap.
892+
// The iterator becomes invalid if the bitmap is modified (e.g., with Add or Remove).
893+
func (rb *Bitmap) UnsetIterator(min, max uint32) IntIterable {
894+
p := new(unsetIterator)
895+
p.Initialize(rb, min, max)
896+
return p
897+
}
898+
827899
// Clone creates a copy of the Bitmap
828900
func (rb *Bitmap) Clone() *Bitmap {
829901
ptr := new(Bitmap)

0 commit comments

Comments
 (0)