Skip to content

Commit cb8366b

Browse files
committed
Merge remote-tracking branch 'origin/master' into version-bumps
2 parents 5d8ad09 + 5155dd6 commit cb8366b

File tree

9 files changed

+452
-40
lines changed

9 files changed

+452
-40
lines changed

README.md

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,12 @@ import scala.collection.decorators._
3535

3636
The following operations are provided:
3737

38-
- `Seq`
39-
- [`intersperse`](https://static.javadoc.io/org.scala-lang.modules/scala-collection-contrib_2.13/0.2.0/scala/collection/decorators/SeqDecorator.html#intersperse[B>:SeqDecorator.this.seq.A,That]\(start:B,sep:B,end:B\)\(implicitbf:scala.collection.BuildFrom[C,B,That]\):That)
40-
- [`replaced`](https://static.javadoc.io/org.scala-lang.modules/scala-collection-contrib_2.13/0.2.0/scala/collection/decorators/SeqDecorator.html#replaced[B>:SeqDecorator.this.seq.A,That]\(elem:B,replacement:B\)\(implicitbf:scala.collection.BuildFrom[C,B,That]\):That)
41-
- `Map`
42-
- [`zipByKey`](https://static.javadoc.io/org.scala-lang.modules/scala-collection-contrib_2.13/0.2.0/scala/collection/decorators/MapDecorator.html#zipByKey[W,That]\(other:scala.collection.Map[MapDecorator.this.map.K,W]\)\(implicitbf:scala.collection.BuildFrom[C,\(MapDecorator.this.map.K,\(MapDecorator.this.map.V,W\)\),That]\):That) / [`join`](https://static.javadoc.io/org.scala-lang.modules/scala-collection-contrib_2.13/0.2.0/scala/collection/decorators/MapDecorator.html#join[W,That]\(other:scala.collection.Map[MapDecorator.this.map.K,W]\)\(implicitbf:scala.collection.BuildFrom[C,\(MapDecorator.this.map.K,\(MapDecorator.this.map.V,W\)\),That]\):That) / [`zipByKeyWith`](https://static.javadoc.io/org.scala-lang.modules/scala-collection-contrib_2.13/0.2.0/scala/collection/decorators/MapDecorator.html#zipByKeyWith[W,X,That]\(other:scala.collection.Map[MapDecorator.this.map.K,W]\)\(f:\(MapDecorator.this.map.V,W\)=>X\)\(implicitbf:scala.collection.BuildFrom[C,\(MapDecorator.this.map.K,X\),That]\):That)
43-
- [`mergeByKey`](https://static.javadoc.io/org.scala-lang.modules/scala-collection-contrib_2.13/0.2.0/scala/collection/decorators/MapDecorator.html#mergeByKey[W,That]\(other:scala.collection.Map[MapDecorator.this.map.K,W]\)\(implicitbf:scala.collection.BuildFrom[C,\(MapDecorator.this.map.K,\(Option[MapDecorator.this.map.V],Option[W]\)\),That]\):That) / [`fullOuterJoin`](https://static.javadoc.io/org.scala-lang.modules/scala-collection-contrib_2.13/0.2.0/scala/collection/decorators/MapDecorator.html#fullOuterJoin[W,That]\(other:scala.collection.Map[MapDecorator.this.map.K,W]\)\(implicitbf:scala.collection.BuildFrom[C,\(MapDecorator.this.map.K,\(Option[MapDecorator.this.map.V],Option[W]\)\),That]\):That) / [`mergeByKeyWith`](https://static.javadoc.io/org.scala-lang.modules/scala-collection-contrib_2.13/0.2.0/scala/collection/decorators/MapDecorator.html#mergeByKeyWith[W,X,That]\(other:scala.collection.Map[MapDecorator.this.map.K,W]\)\(f:PartialFunction[\(Option[MapDecorator.this.map.V],Option[W]\),X]\)\(implicitbf:scala.collection.BuildFrom[C,\(MapDecorator.this.map.K,X\),That]\):That) / [`leftOuterJoin`](https://static.javadoc.io/org.scala-lang.modules/scala-collection-contrib_2.13/0.2.0/scala/collection/decorators/MapDecorator.html#leftOuterJoin[W,That]\(other:scala.collection.Map[MapDecorator.this.map.K,W]\)\(implicitbf:scala.collection.BuildFrom[C,\(MapDecorator.this.map.K,\(MapDecorator.this.map.V,Option[W]\)\),That]\):That) / [`rightOuterJoin`](https://static.javadoc.io/org.scala-lang.modules/scala-collection-contrib_2.13/0.2.0/scala/collection/decorators/MapDecorator.html#rightOuterJoin[W,That]\(other:scala.collection.Map[MapDecorator.this.map.K,W]\)\(implicitbf:scala.collection.BuildFrom[C,\(MapDecorator.this.map.K,\(Option[MapDecorator.this.map.V],W\)\),That]\):That)
44-
- `BitSet`
45-
- [`<<`](https://static.javadoc.io/org.scala-lang.modules/scala-collection-contrib_2.13/0.2.0/scala/collection/decorators/BitSetDecorator.html)
46-
- [`>>`](https://static.javadoc.io/org.scala-lang.modules/scala-collection-contrib_2.13/0.2.0/scala/collection/decorators/BitSetDecorator.html)
38+
- [`BitSet`](https://static.javadoc.io/org.scala-lang.modules/scala-collection-contrib_2.13/0.2.0/scala/collection/decorators/BitSetDecorator.html): <<, >>, ...
39+
- [`Iterable`](https://static.javadoc.io/org.scala-lang.modules/scala-collection-contrib_2.13/0.2.0/scala/collection/decorators/IterableDecorator.html): foldSomeLeft, lazyFoldLeft, lazyFoldRight, splitBy, ...
40+
- [`Iterator`](https://static.javadoc.io/org.scala-lang.modules/scala-collection-contrib_2.13/0.2.0/scala/collection/decorators/IteratorDecorator.html): intersperse, foldSomeLeft, lazyFoldLeft, lazyFoldRight, splitBy, ...
41+
- [`Map`](https://static.javadoc.io/org.scala-lang.modules/scala-collection-contrib_2.13/0.2.0/scala/collection/decorators/MapDecorator.html): fullOuterJoin, leftOuterJoin, mergeByKey, mergeByKeyWith, rightOuterJoin, zipByKey, zipByKeyWith, ...
42+
- [`Seq`](https://static.javadoc.io/org.scala-lang.modules/scala-collection-contrib_2.13/0.2.0/scala/collection/decorators/SeqDecorator.html): intersperse, replaced, splitBy, ...
43+
4744

4845
## Maintenance status
4946

build.sh

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,7 @@ set -e
1212

1313
# When a new binary incompatible Scala version becomes available, a previously released version
1414
# can be released using that new Scala version by creating a new tag containing the Scala version
15-
# after a hash, e.g., v1.2.3#2.13.0-M3. In this situation, the first job of the travis job
16-
# matrix builds the release. All other jobs are stopped. Make sure that the first job uses
17-
# the desired JVM version.
15+
# after a hash, e.g., v1.2.3#2.13.0-M3.
1816

1917
# For normal tags that are cross-built, we release on JDK 8 for Scala 2.x
2018
isReleaseJob() {

src/main/scala/scala/collection/decorators/BitSetDecorator.scala

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ class BitSetDecorator[+C <: BitSet with BitSetOps[C]](protected val bs: C) {
88
import BitSetOps._
99

1010
/**
11-
* Bitwise left shift of this BitSet by given the shift distance.
11+
* Bitwise left shift of this BitSet by the given shift distance.
1212
* The shift distance may be negative, in which case this method performs a right shift.
1313
* @param shiftBy shift distance, in bits
1414
* @return a new BitSet whose value is a bitwise shift left of this BitSet by given shift distance (`shiftBy`)
@@ -44,8 +44,13 @@ class BitSetDecorator[+C <: BitSet with BitSetOps[C]](protected val bs: C) {
4444
val bitOffset = shiftBy & WordMask
4545
val wordOffset = shiftBy >>> LogWL
4646

47+
var significantWordCount = bs.nwords
48+
while (significantWordCount > 0 && bs.word(significantWordCount - 1) == 0) {
49+
significantWordCount -= 1
50+
}
51+
4752
if (bitOffset == 0) {
48-
val newSize = bs.nwords + wordOffset
53+
val newSize = significantWordCount + wordOffset
4954
require(newSize <= MaxSize)
5055
val newBits = Array.ofDim[Long](newSize)
5156
var i = wordOffset
@@ -56,14 +61,14 @@ class BitSetDecorator[+C <: BitSet with BitSetOps[C]](protected val bs: C) {
5661
newBits
5762
} else {
5863
val revBitOffset = WordLength - bitOffset
59-
val extraBits = bs.word(bs.nwords - 1) >>> revBitOffset
64+
val extraBits = bs.word(significantWordCount - 1) >>> revBitOffset
6065
val extraWordCount = if (extraBits == 0) 0 else 1
61-
val newSize = bs.nwords + wordOffset + extraWordCount
66+
val newSize = significantWordCount + wordOffset + extraWordCount
6267
require(newSize <= MaxSize)
6368
val newBits = Array.ofDim[Long](newSize)
6469
var previous = 0L
6570
var i = 0
66-
while (i < bs.nwords) {
71+
while (i < significantWordCount) {
6772
val current = bs.word(i)
6873
newBits(i + wordOffset) = (previous >>> revBitOffset) | (current << bitOffset)
6974
previous = current

src/main/scala/scala/collection/decorators/IteratorDecorator.scala

Lines changed: 92 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,41 @@ package decorators
33

44
import scala.annotation.tailrec
55

6+
/** Enriches Iterator with additional methods.
7+
*
8+
* @define mayNotTerminateInf
9+
* Note: may not terminate for infinite iterators.
10+
* @define doesNotTerminateInf
11+
* Note: does not terminate for infinite iterators.
12+
* @define consumesIterator
13+
* After calling this method, one should discard the iterator it was called
14+
* on. Using it is undefined and subject to change.
15+
* @define consumesAndProducesIterator
16+
* After calling this method, one should discard the iterator it was called
17+
* on, and use only the iterator that was returned. Using the old iterator
18+
* is undefined, subject to change, and may result in changes to the new
19+
* iterator as well.
20+
* @define pseudoCodeExample
21+
* The `===` operator in this pseudo code stands for 'is equivalent to';
22+
* both sides of the `===` give the same result.
23+
*/
624
class IteratorDecorator[A](val `this`: Iterator[A]) extends AnyVal {
725

26+
/**
27+
* Inserts a separator value between each element.
28+
*
29+
* {{{
30+
* Iterator(1, 2, 3).intersperse(0) === Iterator(1, 0, 2, 0, 3)
31+
* Iterator('a', 'b', 'c').intersperse(',') === Iterator('a', ',', 'b', ',', 'c')
32+
* Iterator('a').intersperse(',') === Iterator('a')
33+
* Iterator().intersperse(',') === Iterator()
34+
* }}}
35+
* $pseudoCodeExample
36+
*
37+
* @param sep the separator value.
38+
* @return The resulting iterator contains all elements from the source iterator, separated by the `sep` value.
39+
* @note Reuse: $consumesAndProducesIterator
40+
*/
841
def intersperse[B >: A](sep: B): Iterator[B] = new Iterator[B] {
942
var intersperseNext = false
1043
override def hasNext = intersperseNext || `this`.hasNext
@@ -15,6 +48,27 @@ class IteratorDecorator[A](val `this`: Iterator[A]) extends AnyVal {
1548
}
1649
}
1750

51+
/**
52+
* Inserts a start value at the start of the iterator, a separator value between each element, and
53+
* an end value at the end of the iterator.
54+
*
55+
* {{{
56+
* Iterator(1, 2, 3).intersperse(-1, 0, 99) === Iterator(-1, 1, 0, 2, 0, 3, 99)
57+
* Iterator('a', 'b', 'c').intersperse('[', ',', ']') === Iterator('[', 'a', ',', 'b', ',', 'c', ']')
58+
* Iterator('a').intersperse('[', ',', ']') === Iterator('[', 'a', ']')
59+
* Iterator().intersperse('[', ',', ']') === Iterator('[', ']')
60+
* }}}
61+
* $pseudoCodeExample
62+
*
63+
* @param start the starting value.
64+
* @param sep the separator value.
65+
* @param end the ending value.
66+
* @return The resulting iterator
67+
* begins with the `start` value and ends with the `end` value.
68+
* Inside, are all elements from the source iterator separated by
69+
* the `sep` value.
70+
* @note Reuse: $consumesAndProducesIterator
71+
*/
1872
def intersperse[B >: A](start: B, sep: B, end: B): Iterator[B] = new Iterator[B] {
1973
var started = false
2074
var finished = false
@@ -41,6 +95,27 @@ class IteratorDecorator[A](val `this`: Iterator[A]) extends AnyVal {
4195
}
4296
}
4397

98+
/**
99+
* Folds elements with combination function `op` until
100+
* all elements have been processed, or `op` returns `None`.
101+
* $mayNotTerminateInf
102+
*
103+
* {{{
104+
* def sumOp(acc: Int, e: Int): Option[Int] = if (e == 4) None else Some(acc + e)
105+
* Iterator.empty.foldSomeLeft(0)(sumOp) === 0
106+
* Iterator(1, 2, 3).foldSomeLeft(0)(sumOp) === 6
107+
* Iterator(1, 2, 3, 4, 5).foldSomeLeft(0)(sumOp) === 6
108+
* }}}
109+
* $pseudoCodeExample
110+
*
111+
* @param z the start value
112+
* @param op the binary operator
113+
* @tparam B the result type of the binary operator
114+
* @return the result of evaluating `op` on the previous result of `op` (or `z` for the first time) and
115+
* elements of the source iterator, stopping when all the elements have been
116+
* iterated or earlier when `op` returns `None`
117+
* @note Reuse: $consumesIterator
118+
*/
44119
def foldSomeLeft[B](z: B)(op: (B, A) => Option[B]): B = {
45120
var result: B = z
46121
while (`this`.hasNext) {
@@ -52,6 +127,10 @@ class IteratorDecorator[A](val `this`: Iterator[A]) extends AnyVal {
52127
result
53128
}
54129

130+
/**
131+
* $mayNotTerminateInf
132+
* @note Reuse: $consumesIterator
133+
*/
55134
def lazyFoldLeft[B](z: B)(op: (B, => A) => B): B = {
56135
var result = z
57136
var finished = false
@@ -66,6 +145,10 @@ class IteratorDecorator[A](val `this`: Iterator[A]) extends AnyVal {
66145
result
67146
}
68147

148+
/**
149+
* $doesNotTerminateInf
150+
* @note Reuse: $consumesIterator
151+
*/
69152
def lazyFoldRight[B](z: B)(op: A => Either[B, B => B]): B = {
70153

71154
def chainEval(x: B, fs: immutable.List[B => B]): B =
@@ -87,26 +170,20 @@ class IteratorDecorator[A](val `this`: Iterator[A]) extends AnyVal {
87170
}
88171

89172
/**
90-
* Constructs an iterator where consecutive elements are accumulated as
91-
* long as the output of f for each element doesn't change.
92-
* <pre>
93-
* Vector(1,2,2,3,3,3,2,2)
94-
* .iterator
95-
* .splitBy(identity)
96-
* .toList
97-
* </pre>
98-
* produces
99-
* <pre>
100-
* List(Seq(1),
101-
* Seq(2,2),
102-
* Seq(3,3,3),
103-
* Seq(2,2))
104-
* </pre>
173+
* Constructs an iterator in which each element is a the sequence of accumulated elements
174+
* from the source iterator that have the same key, where the key is calculated by `f`.
175+
*
176+
* {{{
177+
* Iterator(1,2,2,3,3,3,2,2).splitBy(identity) === Iterator(Seq(1), Seq(2,2), Seq(3,3,3), Seq(2,2))
178+
* Iterator((1,1), (1,2), (2, 3)).splitBy(_._1) === Iterator(Seq((1,1), (1,2)), Seq((2,3)))
179+
* }}}
180+
* $pseudoCodeExample
105181
*
106182
* @param f the function to compute a key for an element
107183
* @tparam K the type of the computed key
108184
* @return an iterator of sequences of the consecutive elements with the
109185
* same key in the original iterator
186+
* @note Reuse: $consumesIterator
110187
*/
111188
def splitBy[K](f: A => K): Iterator[immutable.Seq[A]] =
112189
new AbstractIterator[immutable.Seq[A]] {
Lines changed: 120 additions & 0 deletions
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

Lines changed: 3 additions & 0 deletions
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
}

src/test/scala/scala/collection/decorators/BitSetDecoratorTest.scala

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,13 @@ class BitSetDecoratorTest {
3636
}
3737
}
3838

39+
@Test
40+
def skipZeroWordsOnShiftLeft(): Unit = {
41+
val result = BitSet(5 * 64 - 1) << 64
42+
assertEquals(BitSet(6 * 64 - 1), result)
43+
assertEquals(6, result.nwords)
44+
}
45+
3946
@Test
4047
def shiftEmptyRight(): Unit = {
4148
for (shiftBy <- 0 to 128) {

0 commit comments

Comments
 (0)