From 1fe3e2473ac863138e59cd57be14985943a6203b Mon Sep 17 00:00:00 2001 From: mkeskells Date: Wed, 6 May 2020 00:29:40 +0100 Subject: [PATCH] RedBlackTree with fast mutable builder Cherry picked from e9d811ef..e6e2e3df / #8794 The various approaches used to maintain serialization compatibility in 2.12.x can be dropped as they are obviated by use of serialization proxies in 2.13.x. --- build.sbt | 43 ++ .../collection/generic/Subtractable.scala | 3 - .../collection/immutable/RedBlackTree.scala | 518 +++++++++++++++--- .../scala/collection/immutable/TreeMap.scala | 92 +++- .../scala/collection/immutable/TreeSet.scala | 53 +- test/files/run/red-black-tree-serial.check | 10 + .../scala/collection/immutable/MapTest.scala | 144 +++++ .../collection/immutable/TreeMapTest.scala | 21 + .../collection/immutable/TreeSetTest.scala | 42 ++ .../collection/mutable/TreeSetTest.scala | 35 ++ test/scalacheck/redblacktree.scala | 11 +- 11 files changed, 865 insertions(+), 107 deletions(-) create mode 100644 test/files/run/red-black-tree-serial.check create mode 100644 test/junit/scala/collection/immutable/MapTest.scala diff --git a/build.sbt b/build.sbt index f9eee93516cb..8b4d86f9b3f5 100644 --- a/build.sbt +++ b/build.sbt @@ -456,6 +456,49 @@ val mimaFilterSettings = Seq { ProblemFilters.exclude[MissingClassProblem]("scala.collection.mutable.MutationTracker"), ProblemFilters.exclude[MissingClassProblem]("scala.collection.mutable.MutationTracker$"), ProblemFilters.exclude[MissingClassProblem]("scala.collection.mutable.MutationTracker$CheckedIterator"), + + // Refactor internals of SortedMap/SortedSet for faster building and bulk operations + ProblemFilters.exclude[MissingClassProblem]("scala.collection.immutable.RedBlackTree$RedTree$"), + ProblemFilters.exclude[MissingClassProblem]("scala.collection.immutable.RedBlackTree$BlackTree"), + ProblemFilters.exclude[MissingClassProblem]("scala.collection.immutable.RedBlackTree$BlackTree$"), + ProblemFilters.exclude[MissingClassProblem]("scala.collection.immutable.RedBlackTree$RedTree"), + ProblemFilters.exclude[FinalClassProblem]("scala.collection.immutable.RedBlackTree$Tree"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.immutable.RedBlackTree#Tree.this"), + ProblemFilters.exclude[MissingClassProblem]("scala.collection.immutable.RedBlackTree$SetHelper"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.immutable.TreeMap.tree0"), + ProblemFilters.exclude[MissingClassProblem]("scala.collection.immutable.TreeMap$TreeMapBuilder$adder$"), + ProblemFilters.exclude[MissingClassProblem]("scala.collection.immutable.TreeSet$TreeSetBuilder"), + ProblemFilters.exclude[MissingClassProblem]("scala.collection.immutable.RedBlackTree$MapHelper"), + ProblemFilters.exclude[MissingClassProblem]("scala.collection.immutable.RedBlackTree$Helper"), + ProblemFilters.exclude[MissingClassProblem]("scala.collection.immutable.TreeMap$Adder"), + ProblemFilters.exclude[AbstractClassProblem]("scala.collection.immutable.RedBlackTree$Tree"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.immutable.RedBlackTree#Tree.isMutable"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.immutable.RedBlackTree#Tree.sizeOf"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.immutable.RedBlackTree#Tree.isBlack"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.immutable.RedBlackTree#Tree.isRed"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.immutable.RedBlackTree#Tree.makeImmutable"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.immutable.RedBlackTree#Tree.mutableBlack"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.immutable.RedBlackTree#Tree.mutableWithK"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.immutable.RedBlackTree#Tree.mutableWithV"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.immutable.RedBlackTree#Tree.mutableWithKV"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.immutable.RedBlackTree#Tree.mutableWithLeft"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.immutable.RedBlackTree#Tree.mutableWithRight"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.immutable.RedBlackTree#Tree.mutableWithLeftRight"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.immutable.RedBlackTree#Tree.mutableBlackWithLeft"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.immutable.RedBlackTree#Tree.mutableBlackWithRight"), + ProblemFilters.exclude[DirectAbstractMethodProblem]("scala.collection.immutable.RedBlackTree#Tree.black"), + ProblemFilters.exclude[DirectAbstractMethodProblem]("scala.collection.immutable.RedBlackTree#Tree.red"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.immutable.RedBlackTree#Tree.withKV"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.immutable.RedBlackTree#Tree.withV"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.immutable.RedBlackTree#Tree.withLeft"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.immutable.RedBlackTree#Tree.withRight"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.immutable.RedBlackTree#Tree.blackWithLeft"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.immutable.RedBlackTree#Tree.blackWithRight"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.immutable.RedBlackTree#Tree.withLeftRight"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.immutable.RedBlackTree#Tree.this"), + ProblemFilters.exclude[ReversedAbstractMethodProblem]("scala.collection.immutable.RedBlackTree#Tree.black"), + ProblemFilters.exclude[ReversedAbstractMethodProblem]("scala.collection.immutable.RedBlackTree#Tree.red"), + ProblemFilters.exclude[MissingClassProblem]("scala.collection.immutable.TreeMap$TreeMapBuilder") ), } diff --git a/src/library/scala/collection/generic/Subtractable.scala b/src/library/scala/collection/generic/Subtractable.scala index 1ba282584007..223997f4e972 100644 --- a/src/library/scala/collection/generic/Subtractable.scala +++ b/src/library/scala/collection/generic/Subtractable.scala @@ -14,9 +14,6 @@ package scala package collection package generic -import scala.collection.IterableOnce - - /** This trait represents collection-like objects that can be reduced * using a '+' operator. It defines variants of `-` and `--` * as convenience methods in terms of single-element removal `-`. diff --git a/src/library/scala/collection/immutable/RedBlackTree.scala b/src/library/scala/collection/immutable/RedBlackTree.scala index bd65d4db1364..266fcd2a28fb 100644 --- a/src/library/scala/collection/immutable/RedBlackTree.scala +++ b/src/library/scala/collection/immutable/RedBlackTree.scala @@ -14,12 +14,9 @@ package scala package collection package immutable -import collection.Iterator - +import scala.annotation.meta.{getter, setter} import scala.annotation.tailrec -import scala.annotation.meta.getter - -import java.lang.{Integer, String} +import scala.runtime.Statics.releaseFence /** An object containing the RedBlack tree implementation used by for `TreeMaps` and `TreeSets`. * @@ -45,6 +42,142 @@ private[collection] object RedBlackTree { else if (cmp > 0) lookup(tree.right, x) else tree } + private[immutable] abstract class Helper[A](implicit val ordering: Ordering[A]) { + def beforePublish[B](tree: Tree[A, B]): Tree[A, B] = { + if (tree eq null) tree + else if (tree.isMutable) { + val res = tree.mutableBlack.makeImmutable + releaseFence() + res + } else tree.black + } + /** Create a new balanced tree where `newLeft` replaces `tree.left`. + * tree and newLeft are never null */ + protected[this] final def mutableBalanceLeft[A1, B, B1 >: B](tree: Tree[A1, B], newLeft: Tree[A1, B1]): Tree[A1, B1] = { + // Parameter trees + // tree | newLeft + // -- KV R | nl.L nl.KV nl.R + // | nl.R.L nl.R.KV nl.R.R + //Note - unlike the immutable trees we can't consider tree.left eq newLeft + //as the balance operations may mutate the same object + //but that check was mostly to avoid the object creation + if (newLeft.isRed) { + val newLeft_left = newLeft.left + val newLeft_right = newLeft.right + if (isRedTree(newLeft_left)) { + // RED + // black(nl.L) nl.KV black + // nl.R KV R + val resultLeft = newLeft_left.mutableBlack + val resultRight = tree.mutableBlackWithLeft(newLeft_right) + + newLeft.mutableWithLeftRight(resultLeft, resultRight) + } else if (isRedTree(newLeft_right)) { + // RED + // black nl.R.KV black + // nl.L nl.KV nl.R.L nl.R.R KV R + + val newLeft_right_right = newLeft_right.right + + val resultLeft = newLeft.mutableBlackWithRight(newLeft_right.left) + val resultRight = tree.mutableBlackWithLeft(newLeft_right_right) + + newLeft_right.mutableWithLeftRight(resultLeft, resultRight) + } else { + // tree + // newLeft KV R + tree.mutableWithLeft(newLeft) + } + } else { + // tree + // newLeft KV R + tree.mutableWithLeft(newLeft) + } + } + /** Create a new balanced tree where `newRight` replaces `tree.right`. + * tree and newRight are never null */ + protected[this] final def mutableBalanceRight[A1, B, B1 >: B](tree: Tree[A1, B], newRight: Tree[A1, B1]): Tree[A1, B1] = { + // Parameter trees + // tree | newRight + // L KV -- | nr.L nr.KV nr.R + // | nr.L.L nr.L.KV nr.L.R + //Note - unlike the immutable trees we can't consider tree.right eq newRight + //as the balance operations may mutate the same object + //but that check was mostly to avoid the object creation + if (newRight.isRed) { + val newRight_left = newRight.left + if (isRedTree(newRight_left)) { + // RED + // black nr.L.KV black + // L KV nr.L.L nr.L.R nr.KV nr.R + + val resultLeft = tree.mutableBlackWithRight(newRight_left.left) + val resultRight = newRight.mutableBlackWithLeft(newRight_left.right) + + newRight_left.mutableWithLeftRight(resultLeft, resultRight) + + } else { + val newRight_right = newRight.right + if (isRedTree(newRight_right)) { + // RED + // black nr.KV black(nr.R) + // L KV nr.L + + val resultLeft = tree.mutableBlackWithRight(newRight_left) + val resultRight = newRight_right.mutableBlack + + newRight.mutableWithLeftRight(resultLeft, resultRight) + } else { + // tree + // L KV newRight + tree.mutableWithRight(newRight) + } + } + } else { + // tree + // L KV newRight + tree.mutableWithRight(newRight) + } + } + } + private[immutable] class SetHelper[A](implicit ordering: Ordering[A]) extends Helper[A] { + protected[this] final def mutableUpd(tree: Tree[A, Any], k: A): Tree[A, Any] = + if (tree eq null) { + mutableRedTree(k, (), null, null) + } else if (k.asInstanceOf[AnyRef] eq tree.key.asInstanceOf[AnyRef]) { + tree + } else { + val cmp = ordering.compare(k, tree.key) + if (cmp < 0) + mutableBalanceLeft(tree, mutableUpd(tree.left, k)) + else if (cmp > 0) + mutableBalanceRight(tree, mutableUpd(tree.right, k)) + else if (k != tree.key) + tree.mutableWithK(k) + //Note - in 2.13 remove the above else clause + // due to the different handling of key equality + else tree + } + } + private[immutable] class MapHelper[A, B](implicit ordering: Ordering[A]) extends Helper[A] { + protected[this] final def mutableUpd[B1 >: B](tree: Tree[A, B], k: A, v: B1): Tree[A, B1] = + if (tree eq null) { + mutableRedTree(k, v, null, null) + } else if (k.asInstanceOf[AnyRef] eq tree.key.asInstanceOf[AnyRef]) { + tree.mutableWithV(v) + } else { + val cmp = ordering.compare(k, tree.key) + if (cmp < 0) + mutableBalanceLeft(tree, mutableUpd(tree.left, k, v)) + else if (cmp > 0) + mutableBalanceRight(tree, mutableUpd(tree.right, k, v)) + else if (k != tree.key) + tree.mutableWithKV(k,v) + //Note - in 2.13 remove the above else clause + // due to the different handling of key equality + else tree.mutableWithV(v) + } + } def count(tree: Tree[_, _]) = if (tree eq null) 0 else tree.count def update[A: Ordering, B, B1 >: B](tree: Tree[A, B], k: A, v: B1, overwrite: Boolean): Tree[A, B1] = blacken(upd(tree, k, v, overwrite)) @@ -176,8 +309,8 @@ private[collection] object RedBlackTree { def isBlack(tree: Tree[_, _]) = (tree eq null) || isBlackTree(tree) - @`inline` private[this] def isRedTree(tree: Tree[_, _]) = tree.isInstanceOf[RedTree[_, _]] - @`inline` private[this] def isBlackTree(tree: Tree[_, _]) = tree.isInstanceOf[BlackTree[_, _]] + @`inline` private[this] def isRedTree(tree: Tree[_, _]) = (tree ne null) && tree.isRed + @`inline` private[this] def isBlackTree(tree: Tree[_, _]) = (tree ne null) && tree.isBlack private[this] def blacken[A, B](t: Tree[A, B]): Tree[A, B] = if (t eq null) null else t.black @@ -197,41 +330,39 @@ private[collection] object RedBlackTree { // | nl.R.L nl.R.KV nl.R.R if (tree.left eq newLeft) tree else { - val tree_key = tree.key - val tree_value = tree.value - val tree_right = tree.right - if (isRedTree(newLeft)) { + if (newLeft.isRed) { val newLeft_left = newLeft.left val newLeft_right = newLeft.right if (isRedTree(newLeft_left)) { // RED // black(nl.L) nl.KV black // nl.R KV R - RedTree(newLeft.key, newLeft.value, - newLeft_left.black, - BlackTree(tree_key, tree_value, newLeft_right, tree_right)) + val resultLeft = newLeft_left.black + val resultRight = tree.blackWithLeft(newLeft_right) + + newLeft.withLeftRight(resultLeft, resultRight) } else if (isRedTree(newLeft_right)) { // RED // black nl.R.KV black // nl.L nl.KV nl.R.L nl.R.R KV R - RedTree(newLeft_right.key, newLeft_right.value, - BlackTree(newLeft.key, newLeft.value, newLeft_left, newLeft_right.left), - BlackTree(tree_key, tree_value, newLeft_right.right, tree_right)) + val newLeft_right_right = newLeft_right.right + + val resultLeft = newLeft.blackWithRight(newLeft_right.left) + val resultRight = tree.blackWithLeft(newLeft_right_right) + + newLeft_right.withLeftRight(resultLeft, resultRight) } else { // tree // newLeft KV R - mkTree(isBlack(tree), tree_key, tree_value, - newLeft, - tree_right) + tree.withLeft(newLeft) } } else { // tree // newLeft KV R - mkTree(isBlack(tree), tree_key, tree_value, newLeft, tree_right) + tree.withLeft(newLeft) } } } - /** Create a new balanced tree where `newRight` replaces `tree.right`. */ private[this] def balanceRight[A, B1](tree: Tree[A, B1], newRight: Tree[A, B1]): Tree[A, B1] = { // Parameter trees @@ -240,41 +371,46 @@ private[collection] object RedBlackTree { // | nr.L.L nr.L.KV nr.L.R if (tree.right eq newRight) tree else { - val tree_key = tree.key - val tree_value = tree.value - val tree_left = tree.left - if (isRedTree(newRight)) { + if (newRight.isRed) { val newRight_left = newRight.left - val newRight_right = newRight.right if (isRedTree(newRight_left)) { // RED // black nr.L.KV black // L KV nr.L.L nr.L.R nr.KV nr.R - RedTree(newRight_left.key, newRight_left.value, - BlackTree(tree_key, tree_value, tree_left, newRight_left.left), - BlackTree(newRight.key, newRight.value, newRight_left.right, newRight_right)) - } else if (isRedTree(newRight_right)) { - // RED - // black nr.KV black(nr.R) - // L KV nr.L - RedTree(newRight.key, newRight.value, - BlackTree(tree_key, tree_value, tree_left, newRight_left), - newRight_right.black) + val resultLeft = tree.blackWithRight(newRight_left.left) + val resultRight = newRight.blackWithLeft(newRight_left.right) + + newRight_left.withLeftRight(resultLeft, resultRight) } else { - // tree - // L KV newRight - mkTree(isBlack(tree), tree_key, tree_value, tree_left, newRight) + val newRight_right = newRight.right + if (isRedTree(newRight_right)) { + // RED + // black nr.KV black(nr.R) + // L KV nr.L + val resultLeft = tree.blackWithRight(newRight_left) + val resultRight = newRight_right.black + + newRight.withLeftRight(resultLeft, resultRight) + } else { + // tree + // L KV newRight + tree.withRight(newRight) + } } } else { // tree // L KV newRight - mkTree(isBlack(tree), tree_key, tree_value, tree_left, newRight) + tree.withRight(newRight) } } } private[this] def upd[A, B, B1 >: B](tree: Tree[A, B], k: A, v: B1, overwrite: Boolean)(implicit ordering: Ordering[A]): Tree[A, B1] = if (tree eq null) { RedTree(k, v, null, null) + } else if (k.asInstanceOf[AnyRef] eq tree.key.asInstanceOf[AnyRef]) { + if (overwrite) + tree.withV(v) + else tree } else { val cmp = ordering.compare(k, tree.key) if (cmp < 0) @@ -282,7 +418,7 @@ private[collection] object RedBlackTree { else if (cmp > 0) balanceRight(tree, upd(tree.right, k, v, overwrite)) else if (overwrite && (v.asInstanceOf[AnyRef] ne tree.value.asInstanceOf[AnyRef])) - mkTree(isBlackTree(tree), tree.key, v, tree.left, tree.right) + tree.withV(v) else tree } private[this] def updNth[A, B, B1 >: B](tree: Tree[A, B], idx: Int, k: A, v: B1): Tree[A, B1] = if (tree eq null) { @@ -371,43 +507,277 @@ private[collection] object RedBlackTree { * works on the current implementation of the Scala compiler). * * An alternative is to implement the these classes using plain old Java code... + * + * Mutability + * This implementation encodes both mutable and immutable trees. + * Mutable trees are never exposed to the user code but we get significant reductions in both CPU and allocations + * by maintaining a mutable tree during internal operations, e.g. a builder building a Tree, and the other bulk + * API such as filter or ++ + * + * Mutable trees are only used within the confines of this bulk operation and not shared + * Mutable trees may transition to become immutable by calling beforePublish + * Mutable trees may have child nodes (left and right) which are immutable Trees (this promotes structural sharing) + * + * Immutable trees may only child nodes (left and right) which are immutable Trees, and as such the immutable + * trees the entire transitive subtree is immutable + * + * Colour, mutablity and size encoding + * The colour of the Tree, its mutablity and size are all encoded in the _count field + * The colour is encoded in the top bit (31) of the _count. This allows a mutable tree to change colour without + * additional allocation + * The mutable trees always have bits 0 .. 30 (inclusive) set to 0 + * The immutable trees always have bits 0 .. 30 containing the size of the transitive subtree + * + * Naming + * All of the methods that can yield a mutable result have "mutable" on their name, and generally there + * is another method similarly named with doesn't. This is to aid safety and to reduce the cognitive load when + * reviewing changes. e.g. + * def upd(...) will update an immutable Tree, producing an immutable Tree + * def mutableUpd(...) will update a mutable or immutable Tree and may return a mutable or immutable Tree + * a method that has mutable in its name may return a immutable tree if the operation can reuse the existing tree + * */ - sealed abstract class Tree[A, +B]( - @(`inline` @getter) final val key: A, - @(`inline` @getter) final val value: B, - @(`inline` @getter) final val left: Tree[A, B], - @(`inline` @getter) final val right: Tree[A, B]) + private[immutable] final class Tree[A, +B]( + @(`inline` @getter @setter) private var _key: A, + @(`inline` @getter @setter) private var _value: AnyRef, + @(`inline` @getter @setter) private var _left: Tree[A, _], + @(`inline` @getter @setter) private var _right: Tree[A, _], + @(`inline` @getter @setter) private var _count: Int) { - @(`inline` @getter) final val count: Int = 1 + RedBlackTree.count(left) + RedBlackTree.count(right) - def black: Tree[A, B] - def red: Tree[A, B] - } - final class RedTree[A, +B](key: A, - value: B, - left: Tree[A, B], - right: Tree[A, B]) extends Tree[A, B](key, value, left, right) { - override def black: Tree[A, B] = BlackTree(key, value, left, right) - override def red: Tree[A, B] = this - override def toString: String = "RedTree(" + key + ", " + value + ", " + left + ", " + right + ")" - } - final class BlackTree[A, +B](key: A, - value: B, - left: Tree[A, B], - right: Tree[A, B]) extends Tree[A, B](key, value, left, right) { - override def black: Tree[A, B] = this - override def red: Tree[A, B] = RedTree(key, value, left, right) - override def toString: String = "BlackTree(" + key + ", " + value + ", " + left + ", " + right + ")" - } + @`inline` private[RedBlackTree] def isMutable: Boolean = (_count & colourMask) == 0 + // read only APIs + @`inline` private[RedBlackTree] final def count = { + //devTimeAssert((_count & 0x7FFFFFFF) != 0) + _count & colourMask + } + //retain the colour, and mark as mutable + @`inline` private def mutableRetainingColour = _count & colourBit + + //inlined here to avoid outer object null checks + @`inline` private[RedBlackTree] final def sizeOf(tree:Tree[_,_]) = if (tree eq null) 0 else tree.count + @`inline` private[immutable] final def key = _key + @`inline` private[immutable] final def value = _value.asInstanceOf[B] + //Note - in 2.13 this should be private[RedBlackTree] as its only needed or the 2.12 Old RedBlackTree + @`inline` private[immutable] final def left = _left.asInstanceOf[Tree[A, B]] + //Note - in 2.13 this should be private[RedBlackTree] as its only needed or the 2.12 Old RedBlackTree + @`inline` private[immutable] final def right = _right.asInstanceOf[Tree[A, B]] + //Note - only used in tests outside RedBlackTree + @`inline` private[immutable] final def isBlack = _count < 0 + //Note - only used in tests outside RedBlackTree + @`inline` private[immutable] final def isRed = _count >= 0 + + override def toString: String = s"${if(isRed) "RedTree" else "BlackTree"}($key, $value, $left, $right)" + + //mutable APIs + private[RedBlackTree] def makeImmutable: Tree[A, B] = { + def makeImmutableImpl() = { + if (isMutable) { + var size = 1 + if (_left ne null) { + _left.makeImmutable + size += _left.count + } + if (_right ne null) { + _right.makeImmutable + size += _right.count + } + _count |= size //retains colour + } + this + } + makeImmutableImpl() + this + } + + private[RedBlackTree] def mutableBlack: Tree[A, B] = { + if (isBlack) this + else if (isMutable) { + _count = initialBlackCount + this + } + else new Tree(_key, _value, _left, _right, initialBlackCount) + } +// private[NewRedBlackTree] def mutableRed: Tree[A, B] = { +// if (isRed) this +// else if (mutable) { +// _count = initialRedCount +// this +// } +// else new Tree(_key, _value, _left, _right, initialRedCount) +// } + //Note - in 2.13 remove his method + //due to the handling of keys in 2.13 we never replace a key + private[RedBlackTree] def mutableWithK[B1 >: B](newKey: A): Tree[A, B1] = { + if (newKey.asInstanceOf[AnyRef] eq _key.asInstanceOf[AnyRef]) this + else if (isMutable) { + _key = newKey + this + } else new Tree(newKey, _value.asInstanceOf[AnyRef], _left, _right, mutableRetainingColour) + } + private[RedBlackTree] def mutableWithV[B1 >: B](newValue: B1): Tree[A, B1] = { + if (newValue.asInstanceOf[AnyRef] eq _value.asInstanceOf[AnyRef]) this + else if (isMutable) { + _value = newValue.asInstanceOf[AnyRef] + this + } else new Tree(_key, newValue.asInstanceOf[AnyRef], _left, _right, mutableRetainingColour) + } + //Note - in 2.13 remove his method + //due to the handling of keys in 2.13 we never replace a key + private[RedBlackTree] def mutableWithKV[B1 >: B](newKey: A, newValue: B1): Tree[A, B1] = { + if ((newKey.asInstanceOf[AnyRef] eq _key.asInstanceOf[AnyRef]) && + (newValue.asInstanceOf[AnyRef] eq _value.asInstanceOf[AnyRef])) this + else if (isMutable) { + _key = newKey + _value = newValue.asInstanceOf[AnyRef] + this + } else new Tree(newKey, newValue.asInstanceOf[AnyRef], _left, _right, mutableRetainingColour) + } + private[RedBlackTree] def mutableWithLeft[B1 >: B](newLeft: Tree[A, B1]): Tree[A, B1] = { + if (_left eq newLeft) this + else if (isMutable) { + _left = newLeft + this + } else new Tree(_key, _value, newLeft, _right, mutableRetainingColour) + } + private[RedBlackTree] def mutableWithRight[B1 >: B](newRight: Tree[A, B1]): Tree[A, B1] = { + if (_right eq newRight) this + else if (isMutable) { + _right = newRight + this + } else new Tree(_key, _value, _left, newRight, mutableRetainingColour) + } + private[RedBlackTree] def mutableWithLeftRight[B1 >: B](newLeft: Tree[A, B1], newRight: Tree[A, B1]): Tree[A, B1] = { + if ((_left eq newLeft) && (_right eq newRight)) this + else if (isMutable) { + _left = newLeft + _right = newRight + this + } else new Tree(_key, _value, newLeft, newRight, mutableRetainingColour) + } + private[RedBlackTree] def mutableBlackWithLeft[B1 >: B](newLeft: Tree[A, B1]): Tree[A, B1] = { + if ((_left eq newLeft) && isBlack) this + else if (isMutable) { + _count = initialBlackCount + _left = newLeft + this + } else new Tree(_key, _value, newLeft, _right, initialBlackCount) + } + private[RedBlackTree] def mutableBlackWithRight[B1 >: B](newRight: Tree[A, B1]): Tree[A, B1] = { + if ((_right eq newRight) && isBlack) this + else if (isMutable) { + _count = initialBlackCount + _right = newRight + this + } else new Tree(_key, _value, _left, newRight, initialBlackCount) + } - object RedTree { - @`inline` def apply[A, B](key: A, value: B, left: Tree[A, B], right: Tree[A, B]) = new RedTree(key, value, left, right) - def unapply[A, B](t: RedTree[A, B]) = Some((t.key, t.value, t.left, t.right)) + private[RedBlackTree] def black: Tree[A, B] = { + //assertNotMutable(this) + if (isBlack) this + else new Tree(_key, _value, _left, _right, _count ^ colourBit) + } + private[RedBlackTree] def red: Tree[A, B] = { + //assertNotMutable(this) + if (isRed) this + else new Tree(_key, _value, _left, _right, _count ^ colourBit) + } + private[RedBlackTree] def withKV[B1 >: B](newKey: A, newValue: B1): Tree[A, B1] = { + //assertNotMutable(this) + if ((newKey.asInstanceOf[AnyRef] eq _key.asInstanceOf[AnyRef]) && + (newValue.asInstanceOf[AnyRef] eq _value.asInstanceOf[AnyRef])) this + else new Tree(newKey, newValue.asInstanceOf[AnyRef], _left, _right, _count) + } + private[RedBlackTree] def withV[B1 >: B](newValue: B1): Tree[A, B1] = { + //assertNotMutable(this) + if (newValue.asInstanceOf[AnyRef] eq _value.asInstanceOf[AnyRef]) this + else new Tree(_key, newValue.asInstanceOf[AnyRef], _left, _right, _count) + } + + private[RedBlackTree] def withLeft[B1 >: B](newLeft: Tree[A, B1]): Tree[A, B1] = { + //assertNotMutable(this) + //assertNotMutable(newLeft) + if (newLeft eq _left) this + else { + val size = sizeOf(newLeft) + sizeOf(_right) + 1 + new Tree(key, value.asInstanceOf[AnyRef], newLeft, _right, (_count & colourBit) | size) + } + } + private[RedBlackTree] def withRight[B1 >: B](newRight: Tree[A, B1]): Tree[A, B1] = { + //assertNotMutable(this) + //assertNotMutable(newRight) + if (newRight eq _right) this + else { + val size = sizeOf(_left) + sizeOf(newRight) + 1 + new Tree(key, value.asInstanceOf[AnyRef], _left, newRight, (_count & colourBit) | size) + } + } + private[RedBlackTree] def blackWithLeft[B1 >: B](newLeft: Tree[A, B1]): Tree[A, B1] = { + //assertNotMutable(this) + //assertNotMutable(newLeft) + if ((newLeft eq _left) && isBlack) this + else { + val size = sizeOf(newLeft) + sizeOf(_right) + 1 + new Tree(key, value.asInstanceOf[AnyRef], newLeft, _right, initialBlackCount | size) + } + } + private[RedBlackTree] def blackWithRight[B1 >: B](newRight: Tree[A, B1]): Tree[A, B1] = { + //assertNotMutable(this) + //assertNotMutable(newRight) + if ((newRight eq _right) && isBlack) this + else { + val size = sizeOf(_left) + sizeOf(newRight) + 1 + new Tree(key, value.asInstanceOf[AnyRef], _left, newRight, initialBlackCount | size) + } + } + private[RedBlackTree] def withLeftRight[B1 >: B](newLeft: Tree[A, B1], newRight: Tree[A, B1]): Tree[A, B1] = { + //assertNotMutable(this) + //assertNotMutable(newLeft) + //assertNotMutable(newRight) + if ((newLeft eq _left) && (newRight eq _right)) this + else { + val size = sizeOf(newLeft) + sizeOf(newRight) + 1 + new Tree(key, value.asInstanceOf[AnyRef], newLeft, newRight, (_count & colourBit) | size) + } + } } - object BlackTree { - @`inline` def apply[A, B](key: A, value: B, left: Tree[A, B], right: Tree[A, B]) = new BlackTree(key, value, left, right) - def unapply[A, B](t: BlackTree[A, B]) = Some((t.key, t.value, t.left, t.right)) + //see #Tree docs "Colour, mutablity and size encoding" + //we make these final vals because the optimiser inlines them, without reference to the enclosing module + private[RedBlackTree] final val colourBit = 0x80000000 + //really its ~colourBit but that doesnt get inlined + private[RedBlackTree] final val colourMask = colourBit - 1 + private[RedBlackTree] final val initialBlackCount = colourBit + private[RedBlackTree] final val initialRedCount = 0 + + @`inline` private[RedBlackTree] def mutableRedTree[A, B](key: A, value: B, left: Tree[A, B], right: Tree[A, B]) = new Tree[A,B](key, value.asInstanceOf[AnyRef], left, right, initialRedCount) + @`inline` private[RedBlackTree] def mutableBlackTree[A, B](key: A, value: B, left: Tree[A, B], right: Tree[A, B]) = new Tree[A,B](key, value.asInstanceOf[AnyRef], left, right, initialBlackCount) + + /** create a new immutable red tree. + * left and right may be null + */ + //Note - in 2.13 this should be private[RedBlackTree] as its only needed or the 2.12 Old RedBlackTree + private[immutable] def RedTree[A, B](key: A, value: B, left: Tree[A, B], right: Tree[A, B]): Tree[A, B] = { + //assertNotMutable(left) + //assertNotMutable(right) + val size = sizeOf(left) + sizeOf(right) + 1 + new Tree(key, value.asInstanceOf[AnyRef], left, right, initialRedCount | size) } - + //Note - in 2.13 this should be private[RedBlackTree] as its only needed or the 2.12 Old RedBlackTree + private[immutable] def BlackTree[A, B](key: A, value: B, left: Tree[A, B], right: Tree[A, B]): Tree[A, B] = { + //assertNotMutable(left) + //assertNotMutable(right) + val size = sizeOf(left) + sizeOf(right) + 1 + new Tree(key, value.asInstanceOf[AnyRef], left, right, initialBlackCount | size) + } + @`inline` private def sizeOf(tree:Tree[_,_]) = if (tree eq null) 0 else tree.count + //immutable APIs + //assertions - uncomment decls and callers when changing functionality + // private def devTimeAssert(assertion: Boolean) = { + // //uncomment this during development of the functionality + // assert(assertion) + // } + // private def assertNotMutable(t:Tree[_,_]) = { + // devTimeAssert ((t eq null) || t.count > 0) + // } private[this] abstract class TreeIterator[A, B, R](root: Tree[A, B], start: Option[A])(protected implicit val ordering: Ordering[A]) extends AbstractIterator[R] { protected[this] def nextResult(tree: Tree[A, B]): R diff --git a/src/library/scala/collection/immutable/TreeMap.scala b/src/library/scala/collection/immutable/TreeMap.scala index 46e8c324d479..c17cdb1ed415 100644 --- a/src/library/scala/collection/immutable/TreeMap.scala +++ b/src/library/scala/collection/immutable/TreeMap.scala @@ -14,10 +14,12 @@ package scala package collection package immutable +import scala.annotation.tailrec import scala.collection.Stepper.EfficientSplit import scala.collection.generic.DefaultSerializable import scala.collection.immutable.{RedBlackTree => RB} import scala.collection.mutable.ReusableBuilder +import scala.runtime.AbstractFunction2 /** An immutable SortedMap whose values are stored in a red-black tree. * @@ -76,6 +78,7 @@ final class TreeMap[K, +V] private (private val tree: RB.Tree[K, V])(implicit va with DefaultSerializable { def this()(implicit ordering: Ordering[K]) = this(null)(ordering) + private[immutable] def tree0: RB.Tree[K, V] = tree private[this] def newMapOrSelf[V1 >: V](t: RB.Tree[K, V1]): TreeMap[K, V1] = if(t eq tree) this else new TreeMap[K, V1](t) @@ -139,14 +142,20 @@ final class TreeMap[K, +V] private (private val tree: RB.Tree[K, V])(implicit va newMapOrSelf(that match { case tm: TreeMap[K, V] if ordering == tm.ordering => RB.union(tree, tm.tree) + case ls: LinearSeq[(K,V1)] => + if (ls.isEmpty) tree //to avoid the creation of the adder + else { + val adder = new Adder[V1] + adder.addAll(ls) + adder.finalTree + } case _ => + val adder = new Adder[V1] val it = that.iterator - var t: RB.Tree[K, V1] = tree while (it.hasNext) { - val (k, v) = it.next() - t = RB.update(t, k, v, overwrite = true) + adder.apply(it.next()) } - t + adder.finalTree }) override def removedAll(keys: IterableOnce[K]): TreeMap[K, V] = keys match { @@ -258,6 +267,21 @@ final class TreeMap[K, +V] private (private val tree: RB.Tree[K, V])(implicit va else new TreeMap(t2) } + private final class Adder[B1 >: V] + extends RB.MapHelper[K, B1] with Function1[(K, B1), Unit] { + private var currentMutableTree: RB.Tree[K,B1] = tree0 + def finalTree = beforePublish(currentMutableTree) + override def apply(kv: (K, B1)): Unit = { + currentMutableTree = mutableUpd(currentMutableTree, kv._1, kv._2) + } + @tailrec def addAll(ls: LinearSeq[(K, B1)]): Unit = { + if (!ls.isEmpty) { + val kv = ls.head + currentMutableTree = mutableUpd(currentMutableTree, kv._1, kv._2) + addAll(ls.tail) + } + } + } override def equals(obj: Any): Boolean = obj match { case that: TreeMap[K, V] if ordering == that.ordering => RB.entriesEqual(tree, that.tree) case _ => super.equals(obj) @@ -290,23 +314,57 @@ object TreeMap extends SortedMapFactory[TreeMap] { new TreeMap[K, V](t) } - def newBuilder[K, V](implicit ordering: Ordering[K]): ReusableBuilder[(K, V), TreeMap[K, V]] = new ReusableBuilder[(K, V), TreeMap[K, V]] { - private[this] var tree: RB.Tree[K, V] = null - def addOne(elem: (K, V)): this.type = { tree = RB.update(tree, elem._1, elem._2, overwrite = true); this } + def newBuilder[K, V](implicit ordering: Ordering[K]): ReusableBuilder[(K, V), TreeMap[K, V]] = new TreeMapBuilder[K, V] + + private class TreeMapBuilder[K, V](implicit ordering: Ordering[K]) + extends RB.MapHelper[K, V] + with ReusableBuilder[(K, V), TreeMap[K, V]] { + type Tree = RB.Tree[K, V] + private var tree:Tree = null + + def addOne(elem: (K, V)): this.type = { + tree = mutableUpd(tree, elem._1, elem._2) + this + } + private object adder extends AbstractFunction2[K, V, Unit] { + // we cache tree to avoid the outer access to tree + // in the hot path (apply) + private[this] var accumulator :Tree = null + def addForEach(hasForEach: collection.Map[K, V]): Unit = { + accumulator = tree + hasForEach.foreachEntry(this) + tree = accumulator + // be friendly to GC + accumulator = null + } + + override def apply(key: K, value: V): Unit = { + accumulator = mutableUpd(accumulator, key, value) + } + } + override def addAll(xs: IterableOnce[(K, V)]): this.type = { xs match { - case tm: TreeMap[K, V] if ordering == tm.ordering => - tree = RB.union(tree, tm.tree) - case _ => - val it = xs.iterator - while (it.hasNext) { - val (k, v) = it.next() - tree = RB.update(tree, k, v, overwrite = true) - } + // TODO consider writing a mutable-safe union for TreeSet/TreeMap builder ++= + // for the moment we have to force immutability before the union + // which will waste some time and space + // calling `beforePublish` makes `tree` immutable + case ts: TreeMap[K, V] if ts.ordering == ordering => + if (tree eq null) tree = ts.tree0 + else tree = RB.union(beforePublish(tree), ts.tree0) + case that: collection.Map[K, V] => + //add avoiding creation of tuples + adder.addForEach(that) + case _ => + super.addAll(xs) } this } - def result(): TreeMap[K, V] = if(tree eq null) TreeMap.empty else new TreeMap[K, V](tree) - def clear(): Unit = { tree = null } + + override def clear(): Unit = { + tree = null + } + + override def result(): TreeMap[K, V] = new TreeMap[K, V](beforePublish(tree)) } } diff --git a/src/library/scala/collection/immutable/TreeSet.scala b/src/library/scala/collection/immutable/TreeSet.scala index c6015483967b..df845b90b77f 100644 --- a/src/library/scala/collection/immutable/TreeSet.scala +++ b/src/library/scala/collection/immutable/TreeSet.scala @@ -16,8 +16,9 @@ package immutable import scala.collection.Stepper.EfficientSplit import scala.collection.generic.DefaultSerializable -import scala.collection.immutable.{RedBlackTree => RB} import scala.collection.mutable.ReusableBuilder +import scala.collection.immutable.{RedBlackTree => RB} +import scala.runtime.AbstractFunction1 /** This class implements immutable sorted sets using a tree. @@ -185,7 +186,17 @@ final class TreeSet[A] private[immutable] (private[immutable] val tree: RB.Tree[ override def removedAll(that: IterableOnce[A]): TreeSet[A] = that match { case ts: TreeSet[A] if ordering == ts.ordering => newSetOrSelf(RB.difference(tree, ts.tree)) - case _ => super.removedAll(that) + case _ => + //TODO add an implementation of a mutable subtractor similar to TreeMap + //but at least this doesn't create a TreeSet for each iteration + object sub extends AbstractFunction1[A, Unit] { + var currentTree = tree + override def apply(k: A): Unit = { + currentTree = RB.delete(currentTree, k) + } + } + that.iterator.foreach(sub) + newSetOrSelf(sub.currentTree) } override def intersect(that: collection.Set[A]): TreeSet[A] = that match { @@ -246,20 +257,40 @@ object TreeSet extends SortedIterableFactory[TreeSet] { new TreeSet[E](t) } - def newBuilder[A](implicit ordering: Ordering[A]): ReusableBuilder[A, TreeSet[A]] = new ReusableBuilder[A, TreeSet[A]] { - private[this] var tree: RB.Tree[A, Any] = null - def addOne(elem: A): this.type = { tree = RB.update(tree, elem, null, overwrite = false); this } + def newBuilder[A](implicit ordering: Ordering[A]): ReusableBuilder[A, TreeSet[A]] = new TreeSetBuilder[A] + private class TreeSetBuilder[A](implicit ordering: Ordering[A]) + extends RB.SetHelper[A] + with ReusableBuilder[A, TreeSet[A]] { + type Tree = RB.Tree[A, Any] + private [this] var tree:RB.Tree[A, Any] = null + + override def addOne(elem: A): this.type = { + tree = mutableUpd(tree, elem) + this + } + override def addAll(xs: IterableOnce[A]): this.type = { xs match { - case ts: TreeSet[A] if ordering == ts.ordering => - tree = RB.union(tree, ts.tree) + // TODO consider writing a mutable-safe union for TreeSet/TreeMap builder ++= + // for the moment we have to force immutability before the union + // which will waste some time and space + // calling `beforePublish` makes `tree` immutable + case ts: TreeSet[A] if ts.ordering == ordering => + if (tree eq null) tree = ts.tree + else tree = RB.union(beforePublish(tree), ts.tree)(ordering) + case ts: TreeMap[A, _] if ts.ordering == ordering => + if (tree eq null) tree = ts.tree0 + else tree = RB.union(beforePublish(tree), ts.tree0)(ordering) case _ => - val it = xs.iterator - while (it.hasNext) tree = RB.update(tree, it.next(), null, overwrite = false) + super.addAll(xs) } this } - def result(): TreeSet[A] = if(tree eq null) TreeSet.empty else new TreeSet[A](tree) - def clear(): Unit = { tree = null } + + override def clear(): Unit = { + tree = null + } + + override def result(): TreeSet[A] = new TreeSet[A](beforePublish(tree))(ordering) } } diff --git a/test/files/run/red-black-tree-serial.check b/test/files/run/red-black-tree-serial.check new file mode 100644 index 000000000000..22f0b8ce3cc1 --- /dev/null +++ b/test/files/run/red-black-tree-serial.check @@ -0,0 +1,10 @@ + original - Map(key0 -> value:0, key1 -> value:1, key2 -> value:2, key3 -> value:3, key4 -> value:4, key5 -> value:5, key6 -> value:6, key7 -> value:7, key8 -> value:8, key9 -> value:9) + original class - class scala.collection.immutable.TreeMap + binary - ACED0005737200227363616C612E636F6C6C656374696F6E2E696D6D757461626C652E547265654D6170416E108B62F3AFC20300024C00086F72646572696E677400154C7363616C612F6D6174682F4F72646572696E673B4C0004747265657400314C7363616C612F636F6C6C656374696F6E2F696D6D757461626C652F4E6577526564426C61636B5472656524547265653B78707372001B7363616C612E6D6174682E4F72646572696E6724537472696E6724DD981A719E75481A0200007870737200317363616C612E636F6C6C656374696F6E2E696D6D757461626C652E526564426C61636B5472656524426C61636B54726565CD1C6708A7A754010200007872002C7363616C612E636F6C6C656374696F6E2E696D6D757461626C652E526564426C61636B5472656524547265656BA824B21C96EC32020005490005636F756E744C00036B65797400124C6A6176612F6C616E672F4F626A6563743B4C00046C65667471007E00084C0005726967687471007E00084C000576616C756571007E000878700000000A7400046B6579337371007E0006000000037400046B6579317371007E0006000000017400046B657930707074000776616C75653A307371007E0006000000017400046B657932707074000776616C75653A3274000776616C75653A317371007E0006000000067400046B6579357371007E0006000000017400046B657934707074000776616C75653A347372002F7363616C612E636F6C6C656374696F6E2E696D6D757461626C652E526564426C61636B5472656524526564547265655A6F5AFFBDC0180C0200007871007E0007000000047400046B6579377371007E0006000000017400046B657936707074000776616C75653A367371007E0006000000027400046B657938707371007E0019000000017400046B657939707074000776616C75653A3974000776616C75653A3874000776616C75653A3774000776616C75653A3574000776616C75653A3378 + recovered - Map(key0 -> value:0, key1 -> value:1, key2 -> value:2, key3 -> value:3, key4 -> value:4, key5 -> value:5, key6 -> value:6, key7 -> value:7, key8 -> value:8, key9 -> value:9) + recovered class - class scala.collection.immutable.TreeMap + original - TreeSet(key0, key1, key2, key3, key4, key5, key6, key7, key8, key9) + original class - class scala.collection.immutable.TreeSet + binary - ACED0005737200227363616C612E636F6C6C656374696F6E2E696D6D757461626C652E54726565536574B117552038DB580B0300024C00086F72646572696E677400154C7363616C612F6D6174682F4F72646572696E673B4C0004747265657400314C7363616C612F636F6C6C656374696F6E2F696D6D757461626C652F4E6577526564426C61636B5472656524547265653B78707372001B7363616C612E6D6174682E4F72646572696E6724537472696E6724DD981A719E75481A0200007870737200317363616C612E636F6C6C656374696F6E2E696D6D757461626C652E526564426C61636B5472656524426C61636B54726565CD1C6708A7A754010200007872002C7363616C612E636F6C6C656374696F6E2E696D6D757461626C652E526564426C61636B5472656524547265656BA824B21C96EC32020005490005636F756E744C00036B65797400124C6A6176612F6C616E672F4F626A6563743B4C00046C65667471007E00084C0005726967687471007E00084C000576616C756571007E000878700000000A7400046B6579337371007E0006000000037400046B6579317371007E0006000000017400046B6579307070737200177363616C612E72756E74696D652E426F786564556E697474A67D471DECCB9A02000078707371007E0006000000017400046B657932707071007E001071007E00107371007E0006000000067400046B6579357371007E0006000000017400046B657934707071007E00107372002F7363616C612E636F6C6C656374696F6E2E696D6D757461626C652E526564426C61636B5472656524526564547265655A6F5AFFBDC0180C0200007871007E0007000000047400046B6579377371007E0006000000017400046B657936707071007E00107371007E0006000000027400046B657938707371007E0017000000017400046B657939707071007E001071007E001071007E001071007E001071007E001078 + recovered - TreeSet(key0, key1, key2, key3, key4, key5, key6, key7, key8, key9) + recovered class - class scala.collection.immutable.TreeSet diff --git a/test/junit/scala/collection/immutable/MapTest.scala b/test/junit/scala/collection/immutable/MapTest.scala new file mode 100644 index 000000000000..566c1d6c9976 --- /dev/null +++ b/test/junit/scala/collection/immutable/MapTest.scala @@ -0,0 +1,144 @@ +package scala.collection.immutable + +import org.junit.Assert.assertEquals +import org.junit.Test + +class MapTest { + + @Test def builderCompare1: Unit = { + for (size <- 0 to 100; + start <- 0 to 10; + overwrite <- List(true, false)) { + val tBuilder = TreeMap.newBuilder[String, String] + val sBuilder = SortedMap.newBuilder[String, String] + val control = HashMap.newBuilder[String, String] + for (i <- start until start + size) { + sBuilder += i.toString -> "a" + tBuilder += i.toString -> "a" + control += i.toString -> "a" + } + if (overwrite) { + for (i <- start until start + size) { + sBuilder += i.toString -> "b" + tBuilder += i.toString -> "b" + control += i.toString -> "b" + } + } + val treeMap = tBuilder.result() + val sortMap = sBuilder.result() + val expected = control.result() + + assertEquals(expected.size, treeMap.size) + assertEquals(expected.size, sortMap.size) + + assertEquals(expected, treeMap) + assertEquals(expected, sortMap) + + assertEquals(expected, treeMap.iterator.toMap) + assertEquals(expected, sortMap.iterator.toMap) + + } + } + @Test def builderCompare2: Unit = { + for (size <- 0 to 100; + start <- 0 to 10; + overwrite <- List(true, false)) { + val d1 = for (i <- start until start + size) yield { + i -> "a" + } + val d2 = if (overwrite) { + for (i <- start until start + size) yield { + i -> "b" + } + } else List() + val data = d1 ++ d2 + val treeMap = TreeMap(data: _*) + val sortMap = SortedMap(data: _*) + val expected = HashMap(data: _*) + + assertEquals(expected.size, treeMap.size) + assertEquals(expected.size, sortMap.size) + + assertEquals(expected, treeMap) + assertEquals(expected, sortMap) + + assertEquals(expected, treeMap.iterator.toMap) + assertEquals(expected, sortMap.iterator.toMap) + + } + } + + @Test def addition: Unit = { + for (size <- 0 to 100; + start <- 0 to 10) { + val tBuilder = TreeMap.newBuilder[String, String] + val sBuilder = SortedMap.newBuilder[String, String] + val control = HashMap.newBuilder[String, String] + for (i <- start until start + size) { + sBuilder += i.toString -> "a" + tBuilder += i.toString -> "a" + control += i.toString -> "a" + } + + val treeMap = tBuilder.result() + val sortMap = sBuilder.result() + val expected1 = control.result() + + assertEquals(expected1.size, treeMap.size) + assertEquals(expected1.size, sortMap.size) + + assertEquals(expected1, treeMap) + assertEquals(expected1, sortMap) + + assertEquals(expected1, treeMap.iterator.toMap) + assertEquals(expected1, sortMap.iterator.toMap) + + val addList = List.tabulate(size) { i => + (i + start).toString -> "b" + } + val expected = (expected1 ++ addList).toList.sortBy(_._1) + + val addMap = addList.toMap + val addArray = addList.toArray.toSeq + val addSortSame = SortedMap(addList: _*)(Ordering[String]) + val addSortReverse = SortedMap(addList: _*)(Ordering[String].reverse) + val addTreeSame = TreeMap(addList: _*)(Ordering[String]) + val addTreeReverse = TreeMap(addList: _*)(Ordering[String].reverse) + + def emptySB = SortedMap.newBuilder[String, String] + + def emptyTB = TreeMap.newBuilder[String, String] + + for (form <- List(addList, addMap, addArray, addSortSame, addSortReverse, addTreeSame, addTreeReverse)) { + val info = s"form[${form.getClass.getSimpleName}]=$form, size=$size, start=$start" + + assertEquals(info, expected, (treeMap ++ form).toList) + assertEquals(info, expected, (sortMap ++ form).toList) + + assertEquals(info, expected, (treeMap ++ form.iterator).toList) + assertEquals(info, expected, (sortMap ++ form.iterator).toList) + + assertEquals(info, expected, (form.foldLeft(treeMap) { + _ + _ + }).toList) + assertEquals(info, expected, (form.foldLeft(sortMap) { + _ + _ + }).toList) + + for (form2 <- List(addList, addMap, addArray, addSortSame, addSortReverse, addTreeSame, addTreeReverse)) { + val info2 = s"form2[${form2.getClass.getSimpleName}]=$form2, $info" + + assertEquals(info2, expected, (emptySB ++= form ++= form2).result.toList) + assertEquals(info2, expected, (emptyTB ++= form ++= form2).result.toList) + + assertEquals(info2, expected, ((form.foldLeft(emptySB)(_ += _)) ++= form2).result.toList) + assertEquals(info2, expected, ((form.foldLeft(emptyTB)(_ += _)) ++= form2).result.toList) + + assertEquals(info2, expected, (form2.foldLeft(form.foldLeft(emptySB)(_ += _))(_ += _)).result.toList) + assertEquals(info2, expected, (form2.foldLeft(form.foldLeft(emptySB)(_ += _))(_ += _)).result.toList) + + } + } + } + } +} diff --git a/test/junit/scala/collection/immutable/TreeMapTest.scala b/test/junit/scala/collection/immutable/TreeMapTest.scala index 66d4b7bd7ea1..3d1a05ba2de2 100644 --- a/test/junit/scala/collection/immutable/TreeMapTest.scala +++ b/test/junit/scala/collection/immutable/TreeMapTest.scala @@ -234,4 +234,25 @@ class TreeMapTest extends AllocationTest { val r2 = (x.toSeq ++ y.toSeq).toMap assertEquals(r1, r2) } + + @Test def caseIndependent1: Unit = { + val m = scala.collection.immutable.TreeMap[String, String]()(_ compareToIgnoreCase _) + val r = m ++ Seq("a" -> "1", "A" -> "2") + // Note - in 2.13 this should be + // assertEquals(Map("a" -> "2"), r) + // as keys are retained + assertEquals(Map("A" -> "2"), r) + + } + @Test def caseIndependent2: Unit = { + val m = scala.collection.immutable.TreeMap[String, String]()(_ compareToIgnoreCase _) + val r = Seq("a" -> "1", "A" -> "2").foldLeft (m) { + case (acc, t) => acc + t + } + // Note - in 2.13 this should be + // assertEquals(Map("a" -> "2"), r) + // as keys are retained + assertEquals(Map("A" -> "2"), r) + + } } diff --git a/test/junit/scala/collection/immutable/TreeSetTest.scala b/test/junit/scala/collection/immutable/TreeSetTest.scala index af29c3518bbe..40cd5c4943c4 100644 --- a/test/junit/scala/collection/immutable/TreeSetTest.scala +++ b/test/junit/scala/collection/immutable/TreeSetTest.scala @@ -290,4 +290,46 @@ class TreeSetTest extends AllocationTest { assertIdenticalElements(Set(c0l), TreeSet(c0l).intersect(HashSet(c0r))) assertIdenticalElements(Set(c0l), TreeSet(c0l).intersect(TreeSet(c0r))) } + @Test def built(): Unit = { + val builder = TreeSet.newBuilder[String] + builder += "1" + builder ++= (1 to 100).map(_.toString) + val s = builder.result() + assertEquals("TreeSet(1, 10, 100, 11, 12, 13, 14, 15, 16, 17, 18, 19, 2, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 3, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 4, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 5, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 6, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 7, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 8, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 9, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99)", s.toString) + assertEquals("BlackTree(43, (), BlackTree(25, (), BlackTree(16, (), BlackTree(12, (), BlackTree(10, (), BlackTree(1, (), null, null), BlackTree(11, (), RedTree(100, (), null, null), null)), BlackTree(14, (), BlackTree(13, (), null, null), BlackTree(15, (), null, null))), BlackTree(21, (), BlackTree(2, (), RedTree(18, (), BlackTree(17, (), null, null), BlackTree(19, (), null, null)), BlackTree(20, (), null, null)), BlackTree(23, (), BlackTree(22, (), null, null), BlackTree(24, (), null, null)))), BlackTree(32, (), BlackTree(29, (), BlackTree(27, (), BlackTree(26, (), null, null), BlackTree(28, (), null, null)), BlackTree(30, (), BlackTree(3, (), null, null), BlackTree(31, (), null, null))), BlackTree(4, (), RedTree(36, (), BlackTree(34, (), BlackTree(33, (), null, null), BlackTree(35, (), null, null)), BlackTree(38, (), BlackTree(37, (), null, null), BlackTree(39, (), null, null))), BlackTree(41, (), BlackTree(40, (), null, null), BlackTree(42, (), null, null))))), BlackTree(58, (), BlackTree(50, (), BlackTree(47, (), BlackTree(45, (), BlackTree(44, (), null, null), BlackTree(46, (), null, null)), BlackTree(49, (), BlackTree(48, (), null, null), BlackTree(5, (), null, null))), BlackTree(54, (), BlackTree(52, (), BlackTree(51, (), null, null), BlackTree(53, (), null, null)), BlackTree(56, (), BlackTree(55, (), null, null), BlackTree(57, (), null, null)))), RedTree(72, (), BlackTree(65, (), BlackTree(61, (), BlackTree(6, (), BlackTree(59, (), null, null), BlackTree(60, (), null, null)), BlackTree(63, (), BlackTree(62, (), null, null), BlackTree(64, (), null, null))), BlackTree(69, (), BlackTree(67, (), BlackTree(66, (), null, null), BlackTree(68, (), null, null)), BlackTree(70, (), BlackTree(7, (), null, null), BlackTree(71, (), null, null)))), BlackTree(81, (), BlackTree(76, (), BlackTree(74, (), BlackTree(73, (), null, null), BlackTree(75, (), null, null)), BlackTree(8, (), RedTree(78, (), BlackTree(77, (), null, null), BlackTree(79, (), null, null)), BlackTree(80, (), null, null))), RedTree(89, (), BlackTree(85, (), BlackTree(83, (), BlackTree(82, (), null, null), BlackTree(84, (), null, null)), BlackTree(87, (), BlackTree(86, (), null, null), BlackTree(88, (), null, null))), BlackTree(92, (), BlackTree(90, (), BlackTree(9, (), null, null), BlackTree(91, (), null, null)), RedTree(96, (), BlackTree(94, (), BlackTree(93, (), null, null), BlackTree(95, (), null, null)), BlackTree(98, (), BlackTree(97, (), null, null), BlackTree(99, (), null, null)))))))))", s.tree.toString) + } + + @Test def diff(): Unit = { + val src = TreeSet(1, 2, 4, 5) + val removeList = List(1, 2, 7) + val removeVec = Vector(1, 2, 7) + val removeISS = TreeSet(1, 2, 7) + val removeMSS = scala.collection.mutable.TreeSet(1, 2, 7) + val removeIBS = BitSet(1, 2, 7) + val removeMBS = scala.collection.mutable.BitSet(1, 2, 7) + + val expected = SortedSet(4, 5) + for (set <- scala.List[collection.Set[Int]](removeISS, removeMSS, removeIBS, removeMBS)) { + assertEquals(expected, src diff set) + assertEquals(expected, src &~ set) + assertEquals(expected, src filterNot set) + } + + for (set <- scala.List[IterableOnce[Int]](removeList, removeVec, removeVec.iterator, removeISS, removeMSS, removeIBS, removeMBS)) { + assertEquals(expected, src -- set) + } + } + @Test def intersect(): Unit = { + val src = TreeSet(1, 2, 4, 5) + val keepISS = TreeSet(1, 2, 7) + val keepMSS = scala.collection.mutable.TreeSet(1, 2, 7) + val keepIBS = BitSet(1, 2, 7) + val keepMBS = scala.collection.mutable.BitSet(1, 2, 7) + + val expected = SortedSet(1, 2) + for (set <- scala.List[collection.Set[Int]](keepISS, keepMSS, keepIBS, keepMBS)) { + assertEquals(expected, src intersect set) + assertEquals(expected, src filter set) + } + } } diff --git a/test/junit/scala/collection/mutable/TreeSetTest.scala b/test/junit/scala/collection/mutable/TreeSetTest.scala index 1a940e9c741d..1cabb33eaaa0 100644 --- a/test/junit/scala/collection/mutable/TreeSetTest.scala +++ b/test/junit/scala/collection/mutable/TreeSetTest.scala @@ -5,6 +5,7 @@ import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.JUnit4 +import scala.collection.immutable.{ List, Vector} import scala.collection.mutable @@ -17,4 +18,38 @@ class TreeSetTest { val set = mutable.TreeSet("a", "b", "c", "d") assertEquals("b", set.range("b", "c").mkString(",")) } + + @Test def diff(): Unit = { + val src = TreeSet(1, 2, 4, 5) + val removeList = List(1, 2, 7) + val removeVec = Vector(1, 2, 7) + val removeISS = TreeSet(1, 2, 7) + val removeMSS = scala.collection.immutable.TreeSet(1, 2, 7) + val removeIBS = BitSet(1, 2, 7) + val removeMBS = scala.collection.immutable.BitSet(1, 2, 7) + + val expected = SortedSet(4, 5) + for (set <- scala.List[collection.Set[Int]](removeISS, removeMSS, removeIBS, removeMBS)) { + assertEquals(expected, src diff set) + assertEquals(expected, src &~ set) + assertEquals(expected, src filterNot set) + } + + for (set <- scala.List[collection.IterableOnce[Int]](removeList, removeVec, removeVec.iterator, removeISS, removeMSS, removeIBS, removeMBS)) { + assertEquals(expected, src -- set) + } + } + @Test def intersect(): Unit = { + val src = TreeSet(1, 2, 4, 5) + val keepISS = TreeSet(1, 2, 7) + val keepMSS = scala.collection.immutable.TreeSet(1, 2, 7) + val keepIBS = BitSet(1, 2, 7) + val keepMBS = scala.collection.immutable.BitSet(1, 2, 7) + + val expected = SortedSet(1, 2) + for (set <- scala.List[collection.Set[Int]](keepISS, keepMSS, keepIBS, keepMBS)) { + assertEquals(expected, src intersect set) + assertEquals(expected, src filter set) + } + } } diff --git a/test/scalacheck/redblacktree.scala b/test/scalacheck/redblacktree.scala index 596d1fe96d12..ea5cab8c1dcd 100644 --- a/test/scalacheck/redblacktree.scala +++ b/test/scalacheck/redblacktree.scala @@ -45,9 +45,9 @@ abstract class RedBlackTreeTest(tname: String) extends Properties(tname) with Re right <- mkTree(nextLevel, !isRed, label + "R") } yield { if (isRed) - RedTree(label + "N", 0, left, right) + RB.RedTree(label + "N", 0, left, right) else - BlackTree(label + "N", 0, left, right) + RB.BlackTree(label + "N", 0, left, right) } } @@ -77,6 +77,13 @@ trait RedBlackTreeInvariants[K, V] { import RB._ + object RedTree { + def unapply[A, B](t: Tree[A, B]) = if ((t ne null) && t.isRed) Some(t.key, t.value, t.left, t.right) else None + } + object BlackTree { + def unapply[A, B](t: Tree[A, B]) = if ((t ne null) && t.isBlack) Some(t.key, t.value, t.left, t.right) else None + } + implicit def ordering: Ordering[K] def rootIsBlack[A](t: Tree[K, V]) = isBlack(t)