Skip to content

Commit 0a1df6b

Browse files
authored
Merge pull request scala#57 from linasm/mutable-bit-set-shifts-in-place
Add in-place shift operations to mutable.BitSet
2 parents 7c01a17 + e51ad97 commit 0a1df6b

File tree

3 files changed

+230
-0
lines changed

3 files changed

+230
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
package scala.collection.decorators
2+
3+
import scala.collection.{BitSetOps, mutable}
4+
5+
class MutableBitSetDecorator(protected val bs: mutable.BitSet) {
6+
7+
import BitSetDecorator._
8+
import BitSetOps._
9+
10+
/**
11+
* Updates this BitSet to the left shift of itself by the given shift distance.
12+
* The shift distance may be negative, in which case this method performs a right shift.
13+
* @param shiftBy shift distance, in bits
14+
* @return the BitSet itself
15+
*/
16+
def <<=(shiftBy: Int): mutable.BitSet = {
17+
18+
if (bs.nwords == 0 || bs.nwords == 1 && bs.word(0) == 0) ()
19+
else if (shiftBy > 0) shiftLeftInPlace(shiftBy)
20+
else if (shiftBy < 0) shiftRightInPlace(-shiftBy)
21+
22+
bs
23+
}
24+
25+
/**
26+
* Updates this BitSet to the right shift of itself by the given shift distance.
27+
* The shift distance may be negative, in which case this method performs a left shift.
28+
* @param shiftBy shift distance, in bits
29+
* @return the BitSet itself
30+
*/
31+
def >>=(shiftBy: Int): mutable.BitSet = {
32+
33+
if (bs.nwords == 0 || bs.nwords == 1 && bs.word(0) == 0) ()
34+
else if (shiftBy > 0) shiftRightInPlace(shiftBy)
35+
else if (shiftBy < 0) shiftLeftInPlace(-shiftBy)
36+
37+
bs
38+
}
39+
40+
private def shiftLeftInPlace(shiftBy: Int): Unit = {
41+
42+
val bitOffset = shiftBy & WordMask
43+
val wordOffset = shiftBy >>> LogWL
44+
45+
var significantWordCount = bs.nwords
46+
while (significantWordCount > 0 && bs.word(significantWordCount - 1) == 0) {
47+
significantWordCount -= 1
48+
}
49+
50+
if (bitOffset == 0) {
51+
val newSize = significantWordCount + wordOffset
52+
require(newSize <= MaxSize)
53+
ensureCapacity(newSize)
54+
System.arraycopy(bs.elems, 0, bs.elems, wordOffset, significantWordCount)
55+
} else {
56+
val revBitOffset = WordLength - bitOffset
57+
val extraBits = bs.elems(significantWordCount - 1) >>> revBitOffset
58+
val extraWordCount = if (extraBits == 0) 0 else 1
59+
val newSize = significantWordCount + wordOffset + extraWordCount
60+
require(newSize <= MaxSize)
61+
ensureCapacity(newSize)
62+
var i = significantWordCount - 1
63+
var previous = bs.elems(i)
64+
while (i > 0) {
65+
val current = bs.elems(i - 1)
66+
bs.elems(i + wordOffset) = (current >>> revBitOffset) | (previous << bitOffset)
67+
previous = current
68+
i -= 1
69+
}
70+
bs.elems(wordOffset) = previous << bitOffset
71+
if (extraWordCount != 0) bs.elems(newSize - 1) = extraBits
72+
}
73+
java.util.Arrays.fill(bs.elems, 0, wordOffset, 0)
74+
}
75+
76+
private def shiftRightInPlace(shiftBy: Int): Unit = {
77+
78+
val bitOffset = shiftBy & WordMask
79+
80+
if (bitOffset == 0) {
81+
val wordOffset = shiftBy >>> LogWL
82+
val newSize = bs.nwords - wordOffset
83+
if (newSize > 0) {
84+
System.arraycopy(bs.elems, wordOffset, bs.elems, 0, newSize)
85+
java.util.Arrays.fill(bs.elems, newSize, bs.nwords, 0)
86+
} else bs.clear()
87+
} else {
88+
val wordOffset = (shiftBy >>> LogWL) + 1
89+
val extraBits = bs.elems(bs.nwords - 1) >>> bitOffset
90+
val extraWordCount = if (extraBits == 0) 0 else 1
91+
val newSize = bs.nwords - wordOffset + extraWordCount
92+
if (newSize > 0) {
93+
val revBitOffset = WordLength - bitOffset
94+
var previous = bs.elems(wordOffset - 1)
95+
var i = wordOffset
96+
while (i < bs.nwords) {
97+
val current = bs.elems(i)
98+
bs.elems(i - wordOffset) = (previous >>> bitOffset) | (current << revBitOffset)
99+
previous = current
100+
i += 1
101+
}
102+
if (extraWordCount != 0) bs.elems(newSize - 1) = extraBits
103+
java.util.Arrays.fill(bs.elems, newSize, bs.nwords, 0)
104+
} else bs.clear()
105+
}
106+
}
107+
108+
protected final def ensureCapacity(idx: Int): Unit = {
109+
// Copied from mutable.BitSet.ensureCapacity (which is inaccessible from here).
110+
require(idx < MaxSize)
111+
if (idx >= bs.nwords) {
112+
var newlen = bs.nwords
113+
while (idx >= newlen) newlen = math.min(newlen * 2, MaxSize)
114+
val elems1 = new Array[Long](newlen)
115+
Array.copy(bs.elems, 0, elems1, 0, bs.nwords)
116+
bs.elems = elems1
117+
}
118+
}
119+
120+
}

src/main/scala/scala/collection/decorators/package.scala

+3
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,7 @@ package object decorators {
2020
implicit def bitSetDecorator[C <: BitSet with BitSetOps[C]](bs: C): BitSetDecorator[C] =
2121
new BitSetDecorator(bs)
2222

23+
implicit def mutableBitSetDecorator(bs: mutable.BitSet): MutableBitSetDecorator =
24+
new MutableBitSetDecorator(bs)
25+
2326
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
package scala.collection.decorators
2+
3+
import org.junit.{Assert, Test}
4+
5+
import scala.collection.mutable.BitSet
6+
7+
class MutableBitSetDecoratorTest {
8+
9+
import Assert.{assertEquals, assertSame}
10+
import BitSet.empty
11+
12+
@Test
13+
def shiftEmptyLeftInPlace(): Unit = {
14+
for (shiftBy <- 0 to 128) {
15+
val bs = empty
16+
bs <<= shiftBy
17+
assertEquals(empty, bs)
18+
assertEquals(empty.nwords, bs.nwords)
19+
}
20+
}
21+
22+
@Test
23+
def shiftLowestBitLeftInPlace(): Unit = {
24+
for (shiftBy <- 0 to 128) {
25+
val bs = BitSet(0)
26+
bs <<= shiftBy
27+
assertEquals(BitSet(shiftBy), bs)
28+
}
29+
}
30+
31+
@Test
32+
def shiftNegativeLeftInPlace(): Unit = {
33+
val bs = BitSet(1)
34+
bs <<= -1
35+
assertEquals(BitSet(0), bs)
36+
}
37+
38+
@Test
39+
def largeShiftLeftInPlace(): Unit = {
40+
for (shiftBy <- 0 to 128) {
41+
val bs = BitSet(0 to 300 by 5: _*)
42+
val expected = bs.map(_ + shiftBy)
43+
bs <<= shiftBy
44+
assertEquals(expected, bs)
45+
}
46+
}
47+
48+
@Test
49+
def skipZeroWordsOnShiftLeftInPlace(): Unit = {
50+
val bs = BitSet(5 * 64 - 1)
51+
bs <<= 64
52+
assertEquals(BitSet(6 * 64 - 1), bs)
53+
assertEquals(8, bs.nwords)
54+
}
55+
56+
@Test
57+
def shiftEmptyRightInPlace(): Unit = {
58+
for (shiftBy <- 0 to 128) {
59+
val bs = empty
60+
bs >>= shiftBy
61+
assertEquals(empty, bs)
62+
assertEquals(empty.nwords, bs.nwords)
63+
}
64+
}
65+
66+
@Test
67+
def shiftLowestBitRightInPlace(): Unit = {
68+
val bs = BitSet(0)
69+
bs >>= 0
70+
assertEquals(BitSet(0), bs)
71+
72+
for (shiftBy <- 1 to 128) {
73+
val bs = BitSet(0)
74+
bs >>= shiftBy
75+
assertEquals(empty, bs)
76+
}
77+
}
78+
79+
@Test
80+
def shiftToLowestBitRightInPlace(): Unit = {
81+
for (shiftBy <- 0 to 128) {
82+
val bs = BitSet(shiftBy)
83+
bs >>= shiftBy
84+
assertEquals(BitSet(0), bs)
85+
}
86+
}
87+
88+
@Test
89+
def shiftNegativeRightInPlace(): Unit = {
90+
val bs = BitSet(0)
91+
bs >>= -1
92+
assertEquals(BitSet(1), bs)
93+
}
94+
95+
@Test
96+
def largeShiftRightInPlace(): Unit = {
97+
for (shiftBy <- 0 to 128) {
98+
val bs = BitSet(0 to 300 by 5: _*)
99+
val expected = bs.collect {
100+
case b if b >= shiftBy => b - shiftBy
101+
}
102+
bs >>= shiftBy
103+
assertEquals(expected, bs)
104+
}
105+
}
106+
107+
}

0 commit comments

Comments
 (0)