Skip to content

Commit 5687621

Browse files
authored
Merge pull request scala#6750 from retronym/topic/restore-list-filter-opt
Bring back List filter/filterNot optimization to 2.13
2 parents c5da7a0 + ee50542 commit 5687621

File tree

1 file changed

+86
-2
lines changed

1 file changed

+86
-2
lines changed

src/library/scala/collection/immutable/List.scala

Lines changed: 86 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -438,17 +438,101 @@ sealed abstract class List[+A]
438438
loop(null, null, this, this)
439439
}
440440

441+
override def filter(p: A => Boolean): List[A] = filterImpl(p, isFlipped = false)
442+
443+
override def filterNot(p: A => Boolean): List[A] = filterImpl(p, isFlipped = true)
444+
445+
private[this] def filterImpl(p: A => Boolean, isFlipped: Boolean): List[A] = {
446+
447+
// everything seen so far so far is not included
448+
@tailrec def noneIn(l: List[A]): List[A] = {
449+
if (l.isEmpty)
450+
Nil
451+
else {
452+
val h = l.head
453+
val t = l.tail
454+
if (p(h) != isFlipped)
455+
allIn(l, t)
456+
else
457+
noneIn(t)
458+
}
459+
}
460+
461+
// everything from 'start' is included, if everything from this point is in we can return the origin
462+
// start otherwise if we discover an element that is out we must create a new partial list.
463+
@tailrec def allIn(start: List[A], remaining: List[A]): List[A] = {
464+
if (remaining.isEmpty)
465+
start
466+
else {
467+
val x = remaining.head
468+
if (p(x) != isFlipped)
469+
allIn(start, remaining.tail)
470+
else
471+
partialFill(start, remaining)
472+
}
473+
}
474+
475+
// we have seen elements that should be included then one that should be excluded, start building
476+
def partialFill(origStart: List[A], firstMiss: List[A]): List[A] = {
477+
val newHead = new ::(origStart.head, Nil)
478+
var toProcess = origStart.tail
479+
var currentLast = newHead
480+
481+
// we know that all elements are :: until at least firstMiss.tail
482+
while (!(toProcess eq firstMiss)) {
483+
val newElem = new ::(toProcess.head, Nil)
484+
currentLast.next = newElem
485+
currentLast = newElem
486+
toProcess = toProcess.tail
487+
}
488+
489+
// at this point newHead points to a list which is a duplicate of all the 'in' elements up to the first miss.
490+
// currentLast is the last element in that list.
491+
492+
// now we are going to try and share as much of the tail as we can, only moving elements across when we have to.
493+
var next = firstMiss.tail
494+
var nextToCopy = next // the next element we would need to copy to our list if we cant share.
495+
while (!next.isEmpty) {
496+
// generally recommended is next.isNonEmpty but this incurs an extra method call.
497+
val head: A = next.head
498+
if (p(head) != isFlipped) {
499+
next = next.tail
500+
} else {
501+
// its not a match - do we have outstanding elements?
502+
while (!(nextToCopy eq next)) {
503+
val newElem = new ::(nextToCopy.head, Nil)
504+
currentLast.next = newElem
505+
currentLast = newElem
506+
nextToCopy = nextToCopy.tail
507+
}
508+
nextToCopy = next.tail
509+
next = next.tail
510+
}
511+
}
512+
513+
// we have remaining elements - they are unchanged attach them to the end
514+
if (!nextToCopy.isEmpty)
515+
currentLast.next = nextToCopy
516+
517+
newHead
518+
}
519+
520+
noneIn(this)
521+
}
522+
441523
final override def toList: List[A] = this
442524

443525
// Override for performance
444526
override def equals(o: scala.Any): Boolean = {
445527
@tailrec def listEq(a: List[_], b: List[_]): Boolean =
446528
(a eq b) || {
447-
if (a.nonEmpty && b.nonEmpty && a.head == b.head) {
529+
val aEmpty = a.isEmpty
530+
val bEmpty = b.isEmpty
531+
if (!(aEmpty || bEmpty) && a.head == b.head) {
448532
listEq(a.tail, b.tail)
449533
}
450534
else {
451-
a.isEmpty && b.isEmpty
535+
aEmpty && bEmpty
452536
}
453537
}
454538

0 commit comments

Comments
 (0)