Skip to content

Commit db937a3

Browse files
committed
feat: add SetRange method to BitSet
1 parent 8c78148 commit db937a3

3 files changed

Lines changed: 110 additions & 0 deletions

File tree

bitset.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,48 @@ func (b *BitSet) SetTo(i uint, value bool) *BitSet {
289289
return b.Clear(i)
290290
}
291291

292+
// SetRange sets bits in [start, end) to 1, the capacity of the bitset is
293+
// automatically increased accordingly.
294+
// Warning: using a very large value for 'end'
295+
// may lead to a memory shortage and a panic: the caller is responsible
296+
// for providing sensible parameters in line with their memory capacity.
297+
func (b *BitSet) SetRange(start, end uint) *BitSet {
298+
if start >= end {
299+
return b
300+
}
301+
302+
if end-1 >= b.length {
303+
b.extendSet(end - 1)
304+
}
305+
306+
startWord := start >> log2WordSize
307+
endWord := (end - 1) >> log2WordSize // inclusive, the word holding bit end-1
308+
309+
// e.g. start = 71 -> wordsIndex(start) = 7
310+
// firstMask = ^uint64(0) << 7 = 0b111111....11110000000
311+
// keeps the bits below start untouched
312+
firstMask := ^uint64(0) << wordsIndex(start)
313+
314+
// e.g. end = 135 -> wordsIndex(-end) = 57, see FlipRange for the
315+
// modular arithmetic of the unary minus
316+
// lastMask = ^uint64(0) >> 57 = 0b00000....0001111111
317+
// keeps the bits from end on untouched
318+
lastMask := ^uint64(0) >> wordsIndex(-end)
319+
320+
if startWord == endWord { // the whole range lives in a single word
321+
b.set[startWord] |= firstMask & lastMask
322+
return b
323+
}
324+
325+
b.set[startWord] |= firstMask
326+
for i := startWord + 1; i < endWord; i++ {
327+
b.set[i] = ^uint64(0)
328+
}
329+
b.set[endWord] |= lastMask
330+
331+
return b
332+
}
333+
292334
// Flip bit at i.
293335
// Warning: using a very large value for 'i'
294336
// may lead to a memory shortage and a panic: the caller is responsible

bitset_benchmark_test.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -670,3 +670,28 @@ func BenchmarkRank(b *testing.B) {
670670
})
671671
}
672672
}
673+
674+
func BenchmarkSetVsSetRange(b *testing.B) {
675+
const (
676+
sz = 100_000
677+
rangeStart = 5_000
678+
rangeEnd = 10_000
679+
)
680+
681+
s := New(uint(sz))
682+
683+
b.Run("Set", func(b *testing.B) {
684+
for i := 0; i < b.N; i++ {
685+
s.ClearAll()
686+
for j := uint(rangeStart); j < rangeEnd; j++ {
687+
s.Set(j)
688+
}
689+
}
690+
})
691+
b.Run("SetRange", func(b *testing.B) {
692+
for i := 0; i < b.N; i++ {
693+
s.ClearAll()
694+
s.SetRange(rangeStart, rangeEnd)
695+
}
696+
})
697+
}

bitset_test.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1969,6 +1969,49 @@ func TestFlipRange(t *testing.T) {
19691969
}
19701970
}
19711971

1972+
func TestSetRange(t *testing.T) {
1973+
b := new(BitSet)
1974+
c := b.SetRange(4, 25)
1975+
if c.length != 25 {
1976+
t.Error("Unexpected value: ", c.length)
1977+
return
1978+
}
1979+
if c.Count() != 21 {
1980+
t.Error("Unexpected value: ", c.Count())
1981+
return
1982+
}
1983+
// an empty range is a no-op, even beyond the current length
1984+
if d := c.SetRange(100, 100); d.length != 25 || d.Count() != 21 {
1985+
t.Error("Unexpected value: ", d.length, d.Count())
1986+
return
1987+
}
1988+
//
1989+
for i := uint(0); i < 256; i++ {
1990+
for j := uint(0); j <= i; j++ {
1991+
bits := New(i)
1992+
bits.SetRange(0, j)
1993+
c := bits.Count()
1994+
if c != j {
1995+
t.Error("Unexpected value: ", c, " expected: ", j)
1996+
return
1997+
}
1998+
}
1999+
}
2000+
// every sub-range of a 256-bit set, checked bit by bit
2001+
for start := uint(0); start < 256; start++ {
2002+
for end := start; end < 256; end++ {
2003+
bits := New(256)
2004+
bits.SetRange(start, end)
2005+
for i := uint(0); i < 256; i++ {
2006+
if bits.Test(i) != (i >= start && i < end) {
2007+
t.Error("Unexpected value at ", i, " for range [", start, ",", end, ")")
2008+
return
2009+
}
2010+
}
2011+
}
2012+
}
2013+
}
2014+
19722015
func TestCopy(t *testing.T) {
19732016
a := New(10)
19742017
if a.Copy(nil) != 0 {

0 commit comments

Comments
 (0)