Skip to content

Commit 9ebb1c3

Browse files
committed
more efficient UnsetIterator implementation
Also add some property-based tests with a small corpus of values. Signed-off-by: Roger Peppe <[email protected]>
1 parent 45a0bac commit 9ebb1c3

File tree

7 files changed

+615
-68
lines changed

7 files changed

+615
-68
lines changed

arraycontainer.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,58 @@ func (ac *arrayContainer) getManyIterator() manyIterable {
6262
return &shortIterator{ac.content, 0}
6363
}
6464

65+
type arrayContainerUnsetIterator struct {
66+
content []uint16
67+
pos int
68+
nextVal int
69+
}
70+
71+
func (acui *arrayContainerUnsetIterator) next() uint16 {
72+
val := acui.nextVal
73+
acui.nextVal++
74+
for acui.pos < len(acui.content) && uint16(acui.nextVal) == acui.content[acui.pos] {
75+
acui.nextVal++
76+
acui.pos++
77+
}
78+
return uint16(val)
79+
}
80+
81+
func (acui *arrayContainerUnsetIterator) hasNext() bool {
82+
return acui.nextVal < 65536
83+
}
84+
85+
func (acui *arrayContainerUnsetIterator) peekNext() uint16 {
86+
return uint16(acui.nextVal)
87+
}
88+
89+
func (acui *arrayContainerUnsetIterator) advanceIfNeeded(minval uint16) {
90+
if !acui.hasNext() || acui.peekNext() >= minval {
91+
return
92+
}
93+
acui.nextVal = int(minval)
94+
acui.pos = binarySearch(acui.content, minval)
95+
if acui.pos < 0 {
96+
acui.pos = -acui.pos - 1
97+
}
98+
for acui.pos < len(acui.content) && uint16(acui.nextVal) == acui.content[acui.pos] {
99+
acui.nextVal++
100+
acui.pos++
101+
}
102+
}
103+
104+
func newArrayContainerUnsetIterator(a *arrayContainer) *arrayContainerUnsetIterator {
105+
acui := &arrayContainerUnsetIterator{content: a.content, pos: 0, nextVal: 0}
106+
for acui.pos < len(acui.content) && uint16(acui.nextVal) == acui.content[acui.pos] {
107+
acui.nextVal++
108+
acui.pos++
109+
}
110+
return acui
111+
}
112+
113+
func (ac *arrayContainer) getUnsetIterator() shortPeekable {
114+
return newArrayContainerUnsetIterator(ac)
115+
}
116+
65117
func (ac *arrayContainer) minimum() uint16 {
66118
return ac.content[0] // assume not empty
67119
}

benchmark_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -600,6 +600,22 @@ func BenchmarkIterateRoaring(b *testing.B) {
600600
})
601601
}
602602
})
603+
b.Run("unsetIterator", func(b *testing.B) {
604+
b.ReportAllocs()
605+
606+
s := Flip(newBitmap(), 0, 0x100000000)
607+
608+
b.ResetTimer()
609+
610+
for j := 0; j < b.N; j++ {
611+
c9 = uint(0)
612+
i := s.UnsetIterator(0, 0xffffffff)
613+
for i.HasNext() {
614+
i.Next()
615+
c9++
616+
}
617+
}
618+
})
603619
}
604620

605621
// go test -bench BenchmarkSparseIterate -run -

bitmapcontainer.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,39 @@ func (bc *bitmapContainer) getManyIterator() manyIterable {
262262
return newBitmapContainerManyIterator(bc)
263263
}
264264

265+
type bitmapContainerUnsetIterator struct {
266+
ptr *bitmapContainer
267+
i int
268+
}
269+
270+
func (bcui *bitmapContainerUnsetIterator) next() uint16 {
271+
j := bcui.i
272+
bcui.i = bcui.ptr.NextUnsetBit(uint(bcui.i) + 1)
273+
return uint16(j)
274+
}
275+
276+
func (bcui *bitmapContainerUnsetIterator) hasNext() bool {
277+
return bcui.i >= 0 && bcui.i < 65536
278+
}
279+
280+
func (bcui *bitmapContainerUnsetIterator) peekNext() uint16 {
281+
return uint16(bcui.i)
282+
}
283+
284+
func (bcui *bitmapContainerUnsetIterator) advanceIfNeeded(minval uint16) {
285+
if bcui.hasNext() && bcui.peekNext() < minval {
286+
bcui.i = bcui.ptr.NextUnsetBit(uint(minval))
287+
}
288+
}
289+
290+
func newBitmapContainerUnsetIterator(a *bitmapContainer) *bitmapContainerUnsetIterator {
291+
return &bitmapContainerUnsetIterator{a, a.NextUnsetBit(0)}
292+
}
293+
294+
func (bc *bitmapContainer) getUnsetIterator() shortPeekable {
295+
return newBitmapContainerUnsetIterator(bc)
296+
}
297+
265298
func (bc *bitmapContainer) getSizeInBytes() int {
266299
return len(bc.bitmap) * 8
267300
}
@@ -1113,6 +1146,29 @@ func (bc *bitmapContainer) NextSetBit(i uint) int {
11131146
return -1
11141147
}
11151148

1149+
func (bc *bitmapContainer) NextUnsetBit(i uint) int {
1150+
var (
1151+
x = i / 64
1152+
length = uint(len(bc.bitmap))
1153+
)
1154+
if x >= length {
1155+
return int(i)
1156+
}
1157+
w := bc.bitmap[x]
1158+
w = w >> uint(i%64)
1159+
w = ^w
1160+
if w != 0 {
1161+
return int(i) + countTrailingZeros(w)
1162+
}
1163+
x++
1164+
for ; x < length; x++ {
1165+
if bc.bitmap[x] != 0xFFFFFFFFFFFFFFFF {
1166+
return int(x*64) + countTrailingZeros(^bc.bitmap[x])
1167+
}
1168+
}
1169+
return int(length * 64)
1170+
}
1171+
11161172
// PrevSetBit returns the previous set bit e.g the previous int packed into the bitmaparray
11171173
func (bc *bitmapContainer) PrevSetBit(i int) int {
11181174
if i < 0 {

0 commit comments

Comments
 (0)