8000 Bring back List filter/filterNot optimization to 2.13 · scala/scala@dc1da28 · GitHub
[go: up one dir, main page]

Skip to content

Commit dc1da28

Browse files
ackratosretronym
authored andcommitted
Bring back List filter/filterNot optimization to 2.13
1 parent c35b69a commit dc1da28

File tree

1 file changed

+82
-0
lines changed

1 file changed

+82
-0
lines changed

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

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,88 @@ sealed abstract class List[+A] extends AbstractSeq[A]
391391
}
392392
}
393393

394+
override def filter(p: A => Boolean): List[A] = filterImpl(p, isFlipped = false)
395+
396+
override def filterNot(p: A => Boolean): List[A] = filterImpl(p, isFlipped = true)
397+
398+
private[this] def filterImpl(p: A => Boolean, isFlipped: Boolean): List[A] = {
399+
400+
// everything seen so far so far is not included
401+
@tailrec def noneIn(l: List[A]): List[A] = {
402+
if (l.isEmpty)
403+
Nil
404+
else {
405+
val h = l.head
406+
val t = l.tail
407+
if (p(h) != isFlipped)
408+
allIn(l, t)
409+
else
410+
noneIn(t)
411+
}
412+
}
413+
414+
// everything from 'start' is included, if everything from this point is in we can return the origin
415+
// start otherwise if we discover an element that is out we must create a new partial list.
416+
@tailrec def allIn(start: List[A], remaining: List[A]): List[A] = {
417+
if (remaining.isEmpty)
418+
start
419+
else {
420+
val x = remaining.head
421+
if (p(x) != isFlipped)
422+
allIn(start, remaining.tail)
423+
else
424+
partialFill(start, remaining)
425+
}
426+
}
427+
428+
// we have seen elements that should be included then one that should be excluded, start building
429+
def partialFill(origStart: List[A], firstMiss: List[A]): List[A] = {
430+
val newHead = new ::(origStart.head, Nil)
431+
var toProcess = origStart.tail
432+
var currentLast = newHead
433+
434+
// we know that all elements are :: until at least firstMiss.tail
435+
while (!(toProcess eq firstMiss)) {
436+
val newElem = new ::(toProcess.head, Nil)
437+
currentLast.tl = newElem
438+
currentLast = newElem
439+
toProcess = toProcess.tail
440+
}
441+
442+
// at this point newHead points to a list which is a duplicate of all the 'in' elements up to the first miss.
443+
// currentLast is the last element in that list.
444+
445+
// now we are going to try and share as much of the tail as we can, only moving elements across when we have to.
446+
var next = firstMiss.tail
447+
var nextToCopy = next // the next element we would need to copy to our list if we cant share.
448+
while (!next.isEmpty) {
449+
// generally recommended is next.isNonEmpty but this incurs an extra method call.
450+
val head: A = next.head
451+
if (p(head) != isFlipped) {
452+
next = next.tail
453+
} else {
454+
// its not a match - do we have outstanding elements?
455+
while (!(nextToCopy eq next)) {
456+
val newElem = new ::(nextToCopy.head, Nil)
457+
currentLast.tl = newElem
458+
currentLast = newElem
459+
nextToCopy = nextToCopy.tail
460+
}
461+
nextToCopy = next.tail
462+
next = next.tail
463+
}
464+
}
465+
466+
// we have remaining elements - they are unchanged attach them to the end
467+
if (!nextToCopy.isEmpty)
468+
currentLast.tl = nextToCopy
469+
470+
newHead
471+
}
472+
473+
noneIn(repr)
474+
}
475+
394476
override def reverse: List[A] = {
395477
var result: List[A] = Nil
396478
var these = this

0 commit comments

Comments
 (0)
0