Skip to content

Commit 6d1c0c8

Browse files
authored
Merge pull request #522 from e2b-dev/efficient-range-iteration
Add Ranges iterator
2 parents 286ea05 + 0a441f7 commit 6d1c0c8

2 files changed

Lines changed: 144 additions & 0 deletions

File tree

iter.go

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,108 @@ func Unset(b *Bitmap, min, max uint32) iter.Seq[uint32] {
4242
}
4343
}
4444
}
45+
46+
// Ranges iterates contiguous ranges of values present in the bitmap as
47+
// half-open [start, endExclusive) pairs. endExclusive is uint64 to represent
48+
// ranges that include MaxUint32. Ranges spanning container boundaries are merged.
49+
func (b *Bitmap) Ranges() iter.Seq2[uint32, uint64] {
50+
return func(yield func(uint32, uint64) bool) {
51+
ra := &b.highlowcontainer
52+
keys := ra.keys
53+
containers := ra.containers
54+
n := len(keys)
55+
56+
var pendingStart, pendingEnd uint64
57+
hasPending := false
58+
59+
emit := func(rStart, rEnd uint64) bool {
60+
if hasPending && rStart <= pendingEnd {
61+
if rEnd > pendingEnd {
62+
pendingEnd = rEnd
63+
}
64+
return true
65+
}
66+
if hasPending {
67+
if !yield(uint32(pendingStart), pendingEnd) {
68+
return false
69+
}
70+
}
71+
pendingStart = rStart
72+
pendingEnd = rEnd
73+
hasPending = true
74+
return true
75+
}
76+
77+
for idx := 0; idx < n; idx++ {
78+
hs := uint64(keys[idx]) << 16
79+
c := containers[idx]
80+
81+
switch t := c.(type) {
82+
case *runContainer16:
83+
for _, iv := range t.iv {
84+
if !emit(hs+uint64(iv.start), hs+uint64(iv.start)+uint64(iv.length)+1) {
85+
return
86+
}
87+
}
88+
89+
case *bitmapContainer:
90+
bm := t.bitmap
91+
length := uint(len(bm))
92+
pos := uint(0)
93+
94+
for pos < length {
95+
if bm[pos] == 0 {
96+
pos++
97+
continue
98+
}
99+
100+
w := bm[pos]
101+
lo := uint(countTrailingZeros(w))
102+
bitStart := pos*64 + lo
103+
104+
ones := uint(countTrailingOnes(w >> lo))
105+
if lo+ones < 64 {
106+
if !emit(hs|uint64(bitStart), hs|uint64(bitStart+ones)) {
107+
return
108+
}
109+
pos = (bitStart + ones) / 64
110+
} else {
111+
pos++
112+
for pos < length && bm[pos] == 0xFFFFFFFFFFFFFFFF {
113+
pos++
114+
}
115+
var bitEnd uint
116+
if pos < length {
117+
bitEnd = pos*64 + uint(countTrailingOnes(bm[pos]))
118+
} else {
119+
bitEnd = length * 64
120+
}
121+
if !emit(hs|uint64(bitStart), hs|uint64(bitEnd)) {
122+
return
123+
}
124+
}
125+
}
126+
127+
case *arrayContainer:
128+
content := t.content
129+
i := 0
130+
for i < len(content) {
131+
start := uint64(content[i])
132+
end := start + 1
133+
i++
134+
for i < len(content) && uint64(content[i]) == end {
135+
end++
136+
i++
137+
}
138+
if !emit(hs|start, hs|end) {
139+
return
140+
}
141+
}
142+
}
143+
}
144+
145+
if hasPending {
146+
yield(uint32(pendingStart), pendingEnd)
147+
}
148+
}
149+
}

iter_test.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -480,3 +480,42 @@ func TestUnsetIteratorPeekable(t *testing.T) {
480480
assert.False(t, it.HasNext())
481481
})
482482
}
483+
484+
func TestRanges(t *testing.T) {
485+
b := New()
486+
b.AddRange(5, 10)
487+
b.AddRange(20, 25)
488+
b.AddRange(100, 105)
489+
var ranges [][2]uint64
490+
for start, end := range b.Ranges() {
491+
ranges = append(ranges, [2]uint64{uint64(start), end})
492+
}
493+
assert.Equal(t, [][2]uint64{{5, 10}, {20, 25}, {100, 105}}, ranges)
494+
495+
// Merges across container boundaries
496+
b2 := New()
497+
b2.AddRange(0xFFF0, 0x10010)
498+
ranges = nil
499+
for start, end := range b2.Ranges() {
500+
ranges = append(ranges, [2]uint64{uint64(start), end})
501+
}
502+
assert.Equal(t, [][2]uint64{{0xFFF0, 0x10010}}, ranges)
503+
504+
// Scattered values become individual ranges
505+
b3 := New()
506+
b3.Add(1)
507+
b3.Add(3)
508+
b3.Add(5)
509+
ranges = nil
510+
for start, end := range b3.Ranges() {
511+
ranges = append(ranges, [2]uint64{uint64(start), end})
512+
}
513+
assert.Equal(t, [][2]uint64{{1, 2}, {3, 4}, {5, 6}}, ranges)
514+
515+
// Empty bitmap
516+
count := 0
517+
for range New().Ranges() {
518+
count++
519+
}
520+
assert.Equal(t, 0, count)
521+
}

0 commit comments

Comments
 (0)