Skip to content

Commit 6a7cdd9

Browse files
committed
[backport] List.filter optimizations from 2.13.x
Binary compatibilty constraints won't let us actually do this as an override in `List` (we tried that originally but reverted.) But we are free to type-case List in the inherited implementation.
1 parent ade16df commit 6a7cdd9

File tree

1 file changed

+91
-6
lines changed

1 file changed

+91
-6
lines changed

library/src/scala/collection/TraversableLike.scala

Lines changed: 91 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,10 @@ package collection
1515

1616
import generic._
1717
import mutable.Builder
18-
import scala.annotation.migration
19-
import scala.annotation.unchecked.{ uncheckedVariance => uV }
18+
import scala.annotation.{migration, tailrec}
19+
import scala.annotation.unchecked.{uncheckedVariance => uV}
2020
import parallel.ParIterable
21+
import scala.collection.immutable.{::, List, Nil}
2122
import scala.language.higherKinds
2223

2324
/** A template trait for traversable collections of type `Traversable[A]`.
@@ -246,11 +247,95 @@ trait TraversableLike[+A, +Repr] extends Any
246247
}
247248

248249
private[scala] def filterImpl(p: A => Boolean, isFlipped: Boolean): Repr = {
249-
val b = newBuilder
250-
for (x <- this)
251-
if (p(x) != isFlipped) b += x
250+
this match {
251+
case as: List[A] =>
252+
filterImplList(as, p, isFlipped).asInstanceOf[Repr]
253+
case _ =>
254+
val b = newBuilder
255+
for (x <- this)
256+
if (p(x) != isFlipped) b += x
257+
258+
b.result
259+
}
260+
}
252261

253-
b.result
262+
private[this] def filterImplList[A](self: List[A], p: A => Boolean, isFlipped: Boolean): List[A] = {
263+
264+
// everything seen so far so far is not included
265+
@tailrec def noneIn(l: List[A]): List[A] = {
266+
if (l.isEmpty)
267+
Nil
268+
else {
269+
val h = l.head
270+
val t = l.tail
271+
if (p(h) != isFlipped)
272+
allIn(l, t)
273+
else
274+
noneIn(t)
275+
}
276+
}
277+
278+
// everything from 'start' is included, if everything from this point is in we can return the origin
279+
// start otherwise if we discover an element that is out we must create a new partial list.
280+
@tailrec def allIn(start: List[A], remaining: List[A]): List[A] = {
281+
if (remaining.isEmpty)
282+
start
283+
else {
284+
val x = remaining.head
285+
if (p(x) != isFlipped)
286+
allIn(start, remaining.tail)
287+
else
288+
partialFill(start, remaining)
289+
}
290+
}
291+
292+
// we have seen elements that should be included then one that should be excluded, start building
293+
def partialFill(origStart: List[A], firstMiss: List[A]): List[A] = {
294+
val newHead = new ::(origStart.head, Nil)
295+
var toProcess = origStart.tail
296+
var currentLast = newHead
297+
298+
// we know that all elements are :: until at least firstMiss.tail
299+
while (!(toProcess eq firstMiss)) {
300+
val newElem = new ::(toProcess.head, Nil)
301+
currentLast.tl = newElem
302+
currentLast = newElem
303+
toProcess = toProcess.tail
304+
}
305+
306+
// at this point newHead points to a list which is a duplicate of all the 'in' elements up to the first miss.
307+
// currentLast is the last element in that list.
308+
309+
// now we are going to try and share as much of the tail as we can, only moving elements across when we have to.
310+
var next = firstMiss.tail
311+
var nextToCopy = next // the next element we would need to copy to our list if we cant share.
312+
while (!next.isEmpty) {
313+
// generally recommended is next.isNonEmpty but this incurs an extra method call.
314+
val head: A = next.head
315+
if (p(head) != isFlipped) {
316+
next = next.tail
317+
} else {
318+
// its not a match - do we have outstanding elements?
319+
while (!(nextToCopy eq next)) {
320+
val newElem = new ::(nextToCopy.head, Nil)
321+
currentLast.tl = newElem
322+
currentLast = newElem
323+
nextToCopy = nextToCopy.tail
324+
}
325+
nextToCopy = next.tail
326+
next = next.tail
327+
}
328+
}
329+
330+
// we have remaining elements - they are unchanged attach them to the end
331+
if (!nextToCopy.isEmpty)
332+
currentLast.tl = nextToCopy
333+
334+
newHead
335+
}
336+
337+
val result = noneIn(self)
338+
result
254339
}
255340

256341
/** Selects all elements of this $coll which satisfy a predicate.

0 commit comments

Comments
 (0)