Skip to content

Commit d047407

Browse files
committed
GroupedIterator improvements
- Specify the size when creating `ArrayBuffer`s - Modify intermediate results in place as much as possible - Don't allocate an `Option` for padding
1 parent ad8bceb commit d047407

File tree

1 file changed

+34
-46
lines changed

1 file changed

+34
-46
lines changed

src/library/scala/collection/Iterator.scala

Lines changed: 34 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -146,19 +146,18 @@ trait Iterator[+A] extends IterableOnce[A] with IterableOnceOps[A, Iterator, Ite
146146
}
147147

148148
/** A flexible iterator for transforming an `Iterator[A]` into an
149-
* Iterator[Seq[A]], with configurable sequence size, step, and
149+
* `Iterator[Seq[A]]`, with configurable sequence size, step, and
150150
* strategy for dealing with elements which don't fit evenly.
151151
*
152152
* Typical uses can be achieved via methods `grouped` and `sliding`.
153153
*/
154154
class GroupedIterator[B >: A](self: Iterator[B], size: Int, step: Int) extends AbstractIterator[immutable.Seq[B]] {
155-
156155
require(size >= 1 && step >= 1, f"size=$size%d and step=$step%d, but both must be positive")
157156

158-
private[this] var buffer: ArrayBuffer[B] = ArrayBuffer() // the buffer
159-
private[this] var filled = false // whether the buffer is "hot"
160-
private[this] var _partial = true // whether we deliver short sequences
161-
private[this] var pad: Option[() => B] = None // what to pad short sequences with
157+
private[this] val group = new ArrayBuffer[B](size) // the group
158+
private[this] var filled = false // whether the group is "hot"
159+
private[this] var partial = true // whether we deliver short sequences
160+
private[this] var pad: () => B = null // what to pad short sequences with
162161

163162
/** Public functions which can be used to configure the iterator before use.
164163
*
@@ -171,9 +170,10 @@ trait Iterator[+A] extends IterableOnce[A] with IterableOnceOps[A, Iterator, Ite
171170
* @note This method is mutually exclusive with `withPartial(true)`.
172171
*/
173172
def withPadding(x: => B): this.type = {
174-
pad = Some(() => x)
173+
pad = () => x
175174
this
176175
}
176+
177177
/** Public functions which can be used to configure the iterator before use.
178178
*
179179
* Select whether the last segment may be returned with less than `size`
@@ -186,10 +186,9 @@ trait Iterator[+A] extends IterableOnce[A] with IterableOnceOps[A, Iterator, Ite
186186
* @note This method is mutually exclusive with `withPadding`.
187187
*/
188188
def withPartial(x: Boolean): this.type = {
189-
_partial = x
190-
if (_partial) // reset pad since otherwise it will take precedence
191-
pad = None
192-
189+
partial = x
190+
// reset pad since otherwise it will take precedence
191+
if (partial) pad = null
193192
this
194193
}
195194

@@ -200,8 +199,8 @@ trait Iterator[+A] extends IterableOnce[A] with IterableOnceOps[A, Iterator, Ite
200199
* so a subsequent self.hasNext would not test self after the
201200
* group was consumed.
202201
*/
203-
private def takeDestructively(size: Int): Seq[B] = {
204-
val buf = new ArrayBuffer[B]
202+
private def takeDestructively(size: Int): ArrayBuffer[B] = {
203+
val buf = new ArrayBuffer[B](size)
205204
var i = 0
206205
// The order of terms in the following condition is important
207206
// here as self.hasNext could be blocking
@@ -212,66 +211,55 @@ trait Iterator[+A] extends IterableOnce[A] with IterableOnceOps[A, Iterator, Ite
212211
buf
213212
}
214213

215-
private def padding(x: Int) = immutable.ArraySeq.untagged.fill(x)(pad.get())
216214
private def gap = (step - size) max 0
217215

218216
private def go(count: Int) = {
219-
val prevSize = buffer.size
217+
val prevSize = group.size
220218
def isFirst = prevSize == 0
219+
val extension = takeDestructively(count)
221220
// If there is padding defined we insert it immediately
222221
// so the rest of the code can be oblivious
223-
val xs: Seq[B] = {
224-
val res = takeDestructively(count)
225-
// was: extra checks so we don't calculate length unless there's reason
226-
// but since we took the group eagerly, just use the fast length
227-
val shortBy = count - res.length
228-
if (shortBy > 0 && pad.isDefined) res ++ padding(shortBy) else res
222+
var shortBy = count - extension.size
223+
if (pad != null) while (shortBy > 0) {
224+
extension += pad()
225+
shortBy -= 1
229226
}
230-
lazy val len = xs.length
231-
lazy val incomplete = len < count
232227

228+
val extSize = extension.size
233229
// if 0 elements are requested, or if the number of newly obtained
234230
// elements is less than the gap between sequences, we are done.
235-
def deliver(howMany: Int) = {
236-
(howMany > 0 && (isFirst || len > gap)) && {
237-
if (!isFirst)
238-
buffer dropInPlace (step min prevSize)
239-
240-
val available =
241-
if (isFirst) len
242-
else howMany min (len - gap)
243-
244-
buffer ++= (xs takeRight available)
231+
def deliver(howMany: Int) =
232+
(howMany > 0 && (isFirst || extSize > gap)) && {
233+
if (!isFirst) group.dropInPlace(step min prevSize)
234+
val available = if (isFirst) extSize else howMany min (extSize - gap)
235+
group ++= extension.takeRightInPlace(available)
245236
filled = true
246237
true
247238
}
248-
}
249239

250-
if (xs.isEmpty) false // self ran out of elements
251-
else if (_partial) deliver(len min size) // if _partial is true, we deliver regardless
252-
else if (incomplete) false // !_partial && incomplete means no more seqs
253-
else if (isFirst) deliver(len) // first element
240+
if (extension.isEmpty) false // self ran out of elements
241+
else if (partial) deliver(extSize min size) // if partial is true, we deliver regardless
242+
else if (extSize < count) false // !partial && extSize < count means no more seqs
243+
else if (isFirst) deliver(extSize) // first element
254244
else deliver(step min size) // the typical case
255245
}
256246

257247
// fill() returns false if no more sequences can be produced
258248
private def fill(): Boolean = {
259249
if (!self.hasNext) false
260250
// the first time we grab size, but after that we grab step
261-
else if (buffer.isEmpty) go(size)
251+
else if (group.isEmpty) go(size)
262252
else go(step)
263253
}
264254

265-
def hasNext = filled || fill()
255+
def hasNext: Boolean = filled || fill()
256+
266257
@throws[NoSuchElementException]
267258
def next(): immutable.Seq[B] = {
268-
if (!filled)
269-
fill()
270-
271-
if (!filled)
272-
throw new NoSuchElementException("next on empty iterator")
259+
if (!filled) fill()
260+
if (!filled) Iterator.empty.next()
273261
filled = false
274-
immutable.ArraySeq.unsafeWrapArray(buffer.toArray[Any]).asInstanceOf[immutable.ArraySeq[B]]
262+
immutable.ArraySeq.unsafeWrapArray(group.toArray[Any]).asInstanceOf[immutable.ArraySeq[B]]
275263
}
276264
}
277265

0 commit comments

Comments
 (0)