diff --git a/.gitignore b/.gitignore index a3e1d7e6e8..9c78f188c3 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ target/ .history .DS_Store /tmp/ +/bin/ diff --git a/src/main/scala/strawman/collection/Iterable.scala b/src/main/scala/strawman/collection/Iterable.scala index 079adcda33..3ab51ba5f5 100644 --- a/src/main/scala/strawman/collection/Iterable.scala +++ b/src/main/scala/strawman/collection/Iterable.scala @@ -1,4 +1,5 @@ -package strawman.collection +package strawman +package collection import scala.annotation.unchecked.uncheckedVariance import scala.reflect.ClassTag @@ -54,8 +55,10 @@ trait IterableOps[+A] extends Any { protected def coll: Iterable[A] private def iterator() = coll.iterator() - /** Apply `f` to each element for tis side effects */ - def foreach(f: A => Unit): Unit = iterator().foreach(f) + /** Apply `f` to each element for tis side effects + * Note: [U] parameter needed to help scalac's type inference. + */ + def foreach[U](f: A => U): Unit = iterator().foreach(f) /** Fold left */ def foldLeft[B](z: B)(op: (B, A) => B): B = iterator().foldLeft(z)(op) @@ -188,3 +191,23 @@ trait IterablePolyTransforms[+A, +C[A]] extends Any { def zip[B](xs: IterableOnce[B]): C[(A @uncheckedVariance, B)] = fromIterable(View.Zip(coll, xs)) // sound bcs of VarianceNote } + +trait TagGuidedPolyTransforms[+A, +C[A]] extends Any { + import scala.reflect.ClassTag + + protected def coll: Iterable[A] + def fromIterable[B: ClassTag](coll: Iterable[B]): C[B] + + /** Map */ + def map[B: ClassTag](f: A => B): C[B] = fromIterable(View.Map(coll, f)) + + /** Flatmap */ + def flatMap[B: ClassTag](f: A => IterableOnce[B]): C[B] = fromIterable(View.FlatMap(coll, f)) + + /** Concatenation */ + def ++[B >: A : ClassTag](xs: IterableOnce[B]): C[B] = fromIterable(View.Concat(coll, xs)) + + /** Zip. Interesting because it requires to align to source collections. */ + def zip[B: ClassTag](xs: IterableOnce[B]): C[(A @uncheckedVariance, B)] = fromIterable(View.Zip(coll, xs)) + // sound bcs of VarianceNote +} diff --git a/src/main/scala/strawman/collection/IterableOnce.scala b/src/main/scala/strawman/collection/IterableOnce.scala index be034b04d4..6748b56be4 100644 --- a/src/main/scala/strawman/collection/IterableOnce.scala +++ b/src/main/scala/strawman/collection/IterableOnce.scala @@ -1,6 +1,5 @@ -package strawman.collection - -import strawman.collection.mutable.Iterator +package strawman +package collection trait IterableOnce[+A] { /** Iterator can be used only once */ diff --git a/src/main/scala/strawman/collection/mutable/Iterator.scala b/src/main/scala/strawman/collection/Iterator.scala similarity index 94% rename from src/main/scala/strawman/collection/mutable/Iterator.scala rename to src/main/scala/strawman/collection/Iterator.scala index 51ab5c7cba..a35f20c7f9 100644 --- a/src/main/scala/strawman/collection/mutable/Iterator.scala +++ b/src/main/scala/strawman/collection/Iterator.scala @@ -1,17 +1,17 @@ -package strawman.collection.mutable +package strawman.collection import scala.{Boolean, Int, Unit, Nothing, NoSuchElementException} -import strawman.collection.{IndexedView, IterableOnce} /** A core Iterator class */ -trait Iterator[+A] { self => +trait Iterator[+A] extends IterableOnce[A] { self => def hasNext: Boolean def next(): A + def iterator() = this def foldLeft[B](z: B)(op: (B, A) => B): B = if (hasNext) foldLeft(op(z, next()))(op) else z def foldRight[B](z: B)(op: (A, B) => B): B = if (hasNext) op(next(), foldRight(z)(op)) else z - def foreach(f: A => Unit): Unit = + def foreach[U](f: A => U): Unit = while (hasNext) f(next()) def indexWhere(p: A => Boolean): Int = { var i = 0 diff --git a/src/main/scala/strawman/collection/Seq.scala b/src/main/scala/strawman/collection/Seq.scala index f28a9849f0..e5789fefca 100644 --- a/src/main/scala/strawman/collection/Seq.scala +++ b/src/main/scala/strawman/collection/Seq.scala @@ -1,7 +1,6 @@ package strawman.collection import scala.{Any, Boolean, Int, IndexOutOfBoundsException} -import strawman.collection.mutable.Iterator import strawman.collection.immutable.{List, Nil} import scala.annotation.unchecked.uncheckedVariance diff --git a/src/main/scala/strawman/collection/Set.scala b/src/main/scala/strawman/collection/Set.scala new file mode 100644 index 0000000000..2fcb34a85f --- /dev/null +++ b/src/main/scala/strawman/collection/Set.scala @@ -0,0 +1,4 @@ +package strawman +package collection + +trait Set[A] extends Iterable[A] {} diff --git a/src/main/scala/strawman/collection/View.scala b/src/main/scala/strawman/collection/View.scala index 5b58ca66d0..3eeb874f13 100644 --- a/src/main/scala/strawman/collection/View.scala +++ b/src/main/scala/strawman/collection/View.scala @@ -2,7 +2,6 @@ package strawman.collection import scala.{Int, Boolean, Nothing, annotation} import scala.Predef.intWrapper -import strawman.collection.mutable.Iterator /** Concrete collection type: View */ trait View[+A] extends Iterable[A] with IterableLike[A, View] { diff --git a/src/main/scala/strawman/collection/immutable/Array.scala b/src/main/scala/strawman/collection/immutable/Array.scala new file mode 100644 index 0000000000..fe572e323c --- /dev/null +++ b/src/main/scala/strawman/collection/immutable/Array.scala @@ -0,0 +1,17 @@ +package strawman.collection.immutable + +import java.lang.Class +import scala.{AnyVal, Int} +import scala.reflect.ClassTag +import strawman.collection.arrayToArrayOps + +class Array[T](private val underlying: scala.Array[T]) extends AnyVal { + def length: Int = underlying.length + def apply(i: Int): T = underlying.apply(i) + def elemClass: Class[_] = underlying.getClass.getComponentType +} + +object Array { + def apply[T: ClassTag](elems: T*): Array[T] = + scala.Array(elems: _*).readOnly +} diff --git a/src/main/scala/strawman/collection/immutable/ArrayOps.scala b/src/main/scala/strawman/collection/immutable/ArrayOps.scala new file mode 100644 index 0000000000..a94cf8299b --- /dev/null +++ b/src/main/scala/strawman/collection/immutable/ArrayOps.scala @@ -0,0 +1,53 @@ +package strawman.collection.immutable + +import strawman.collection.{Iterable, IterableOps, SeqMonoTransforms, ArrayLike, IndexedView, TagGuidedPolyTransforms, arrayToArrayOps} +import strawman.collection.mutable.{Buildable, ArrayBuffer} + +import scala.{Int, AnyVal, AnyRef} +import scala.Predef.String +import scala.reflect.ClassTag + +class ArrayOps[A](val xs: Array[A]) + extends AnyRef + // Note: we would like this to be a value class over `immutable.Array[A]`, + // but scalac complains with + // + // error: value class may not wrap another user-defined value class + // + // This is SI-7685. dotty accepts the value class, however. We should fix + // the problem in scalac, fortunately it's scheduled to arrive for 2.13. + // Until that is the case, we have no choice but to make ArrayOps a + // normal class. + with IterableOps[A] + with SeqMonoTransforms[A, Array[A]] + with TagGuidedPolyTransforms[A, Array] + with Buildable[A, Array[A]] + with ArrayLike[A] { + + override def view = new ArrayView(xs) + + protected def coll = view + def iterator() = coll.iterator() + + def length = xs.length + def apply(i: Int) = xs.apply(i) + + def elemTag: ClassTag[A] = ClassTag(xs.elemClass) + + protected def fromIterableWithSameElemType(coll: Iterable[A]): Array[A] = + coll.toArray[A](elemTag).readOnly + + def fromIterable[B: ClassTag](coll: Iterable[B]): Array[B] = coll.toArray[B].readOnly + + protected[this] def newBuilder = new ArrayBuffer[A].mapResult(_.toArray(elemTag).readOnly) + + override def knownSize = length + + override def className = "immutable.Array" +} + +case class ArrayView[A](xs: Array[A]) extends IndexedView[A] { + def length = xs.length + def apply(n: Int) = xs(n) + override def className = "immutable.ArrayView" +} diff --git a/src/main/scala/strawman/collection/immutable/LazyList.scala b/src/main/scala/strawman/collection/immutable/LazyList.scala index b9cf342948..a4ade7e63f 100644 --- a/src/main/scala/strawman/collection/immutable/LazyList.scala +++ b/src/main/scala/strawman/collection/immutable/LazyList.scala @@ -1,8 +1,7 @@ package strawman.collection.immutable import scala.{Option, Some, None, Nothing, StringContext} -import strawman.collection.{IterableFactory, Iterable, LinearSeq, SeqLike} -import strawman.collection.mutable.Iterator +import strawman.collection.{IterableFactory, Iterable, LinearSeq, SeqLike, Iterator} class LazyList[+A](expr: => LazyList.Evaluated[A]) extends LinearSeq[A] with SeqLike[A, LazyList] { diff --git a/src/main/scala/strawman/collection/javaSupport.scala b/src/main/scala/strawman/collection/immutable/StringOps.scala similarity index 57% rename from src/main/scala/strawman/collection/javaSupport.scala rename to src/main/scala/strawman/collection/immutable/StringOps.scala index 924542f117..4933bdd213 100644 --- a/src/main/scala/strawman/collection/javaSupport.scala +++ b/src/main/scala/strawman/collection/immutable/StringOps.scala @@ -1,9 +1,8 @@ -package strawman.collection +package strawman.collection.immutable -import strawman.collection.immutable.List - -import scala.{Array, Char, Int, AnyVal} +import scala.{Char, Int, AnyVal} import scala.Predef.String +import strawman.collection.{Iterable, IterableOps, SeqMonoTransforms, IterablePolyTransforms, ArrayLike, IndexedView, IterableOnce, stringToStringOps} import strawman.collection.mutable.{ArrayBuffer, Buildable, StringBuilder} import scala.reflect.ClassTag @@ -71,42 +70,3 @@ case class StringView(s: String) extends IndexedView[Char] { def apply(n: Int) = s.charAt(n) override def className = "StringView" } - - -class ArrayOps[A](val xs: Array[A]) - extends AnyVal with IterableOps[A] - with SeqMonoTransforms[A, Array[A]] - with Buildable[A, Array[A]] - with ArrayLike[A] { - - protected def coll = new ArrayView(xs) - def iterator() = coll.iterator() - - def length = xs.length - def apply(i: Int) = xs.apply(i) - - override def view = new ArrayView(xs) - - def elemTag: ClassTag[A] = ClassTag(xs.getClass.getComponentType) - - protected def fromIterableWithSameElemType(coll: Iterable[A]): Array[A] = coll.toArray[A](elemTag) - - def fromIterable[B: ClassTag](coll: Iterable[B]): Array[B] = coll.toArray[B] - - protected[this] def newBuilder = new ArrayBuffer[A].mapResult(_.toArray(elemTag)) - - override def knownSize = xs.length - - override def className = "Array" - - def map[B: ClassTag](f: A => B): Array[B] = fromIterable(View.Map(coll, f)) - def flatMap[B: ClassTag](f: A => IterableOnce[B]): Array[B] = fromIterable(View.FlatMap(coll, f)) - def ++[B >: A : ClassTag](xs: IterableOnce[B]): Array[B] = fromIterable(View.Concat(coll, xs)) - def zip[B: ClassTag](xs: IterableOnce[B]): Array[(A, B)] = fromIterable(View.Zip(coll, xs)) -} - -case class ArrayView[A](xs: Array[A]) extends IndexedView[A] { - def length = xs.length - def apply(n: Int) = xs(n) - override def className = "ArrayView" -} diff --git a/src/main/scala/strawman/collection/mutable/ArrayBuffer.scala b/src/main/scala/strawman/collection/mutable/ArrayBuffer.scala index 740e71f4b8..450db4e774 100644 --- a/src/main/scala/strawman/collection/mutable/ArrayBuffer.scala +++ b/src/main/scala/strawman/collection/mutable/ArrayBuffer.scala @@ -1,28 +1,40 @@ package strawman.collection.mutable -import scala.{Array, Int, Boolean, Unit, AnyRef} +import java.lang.IndexOutOfBoundsException +import scala.{Array, Int, Long, Boolean, Unit, AnyRef} +import strawman.collection.{IterableFactory, Iterable, IterableOnce, SeqLike, IndexedView} import scala.Predef.intWrapper -import strawman.collection.{IndexedView, Iterable, IterableFactory, IterableOnce, Seq, SeqLike} /** Concrete collection type: ArrayBuffer */ class ArrayBuffer[A] private (initElems: Array[AnyRef], initLength: Int) - extends Seq[A] + extends IndexedOptimizedGrowableSeq[A] with SeqLike[A, ArrayBuffer] with Buildable[A, ArrayBuffer[A]] with Builder[A, ArrayBuffer[A]] { def this() = this(new Array[AnyRef](16), 0) - private var elems: Array[AnyRef] = initElems - private var start = 0 + private var array: Array[AnyRef] = initElems private var end = initLength - def apply(n: Int) = elems(start + n).asInstanceOf[A] + /** Ensure that the internal array has at least `n` cells. */ + private def ensureSize(n: Int): Unit = + array = RefArrayUtils.ensureSize(array, end, n) - def length = end - start + /** Reduce length to `n`, nulling out all dropped elements */ + private def reduceToSize(n: Int): Unit = { + RefArrayUtils.nullElems(array, n, end) + end = n + } + + def apply(n: Int) = array(n).asInstanceOf[A] + + def update(n: Int, elem: A): Unit = array(n) = elem.asInstanceOf[AnyRef] + + def length = end override def knownSize = length - override def view = new ArrayBufferView(elems, start, end) + override def view = new ArrayBufferView(array, end) def iterator() = view.iterator() @@ -31,45 +43,76 @@ class ArrayBuffer[A] private (initElems: Array[AnyRef], initLength: Int) protected[this] def newBuilder = new ArrayBuffer[A] + def clear() = + end = 0 + def +=(elem: A): this.type = { - if (end == elems.length) { - if (start > 0) { - Array.copy(elems, start, elems, 0, length) - end -= start - start = 0 - } - else { - val newelems = new Array[AnyRef](end * 2) - Array.copy(elems, 0, newelems, 0, end) - elems = newelems - } - } - elems(end) = elem.asInstanceOf[AnyRef] + ensureSize(end + 1) + this(end) = elem end += 1 this } + /** Overridden to use array copying for efficiency where possible. */ + override def ++=(elems: IterableOnce[A]): this.type = { + elems match { + case elems: ArrayBuffer[_] => + ensureSize(length + elems.length) + Array.copy(elems.array, 0, array, length, elems.length) + end = length + elems.length + case _ => super.++=(elems) + } + this + } + def result = this - /** New operation: destructively drop elements at start of buffer. */ - def trimStart(n: Int): Unit = start += (n max 0) + def insert(idx: Int, elem: A): Unit = { + if (idx < 0 || idx > end) throw new IndexOutOfBoundsException + ensureSize(end + 1) + Array.copy(array, idx, array, idx + 1, end - idx) + this(idx) = elem + } - /** Overridden to use array copying for efficiency where possible. */ - override def ++[B >: A](xs: IterableOnce[B]): ArrayBuffer[B] = xs match { - case xs: ArrayBuffer[B] => - val elems = new Array[AnyRef](length + xs.length) - Array.copy(this.elems, this.start, elems, 0, this.length) - Array.copy(xs.elems, xs.start, elems, this.length, xs.length) - new ArrayBuffer(elems, elems.length) - case _ => super.++(xs) + def insertAll(idx: Int, elems: IterableOnce[A]): Unit = { + if (idx < 0 || idx > end) throw new IndexOutOfBoundsException + elems match { + case elems: Iterable[A] => + val elemsLength = elems.size + ensureSize(length + elemsLength) + Array.copy(array, idx, array, idx + elemsLength, end - idx) + elems match { + case elems: ArrayBuffer[_] => + Array.copy(elems.array, 0, array, idx, elemsLength) + case _ => + var i = 0 + val it = elems.iterator() + while (i < elemsLength) { + this(idx + i) = it.next() + i += 1 + } + } + case _ => + val buf = new ArrayBuffer() ++= elems + insertAll(idx, buf) + } } - override def take(n: Int) = { - val elems = new Array[AnyRef](n min length) - Array.copy(this.elems, this.start, elems, 0, elems.length) - new ArrayBuffer(elems, elems.length) + def remove(idx: Int): A = { + if (idx < 0 || idx >= end) throw new IndexOutOfBoundsException + val res = this(idx) + Array.copy(array, idx + 1, array, idx, end - (idx + 1)) + reduceToSize(end - 1) + res } + def remove(from: Int, n: Int): Unit = + if (n > 0) { + if (from < 0 || from + n > end) throw new IndexOutOfBoundsException + Array.copy(array, from + n, array, from, end - (from + n)) + reduceToSize(end - n) + } + override def className = "ArrayBuffer" } @@ -78,16 +121,46 @@ object ArrayBuffer extends IterableFactory[ArrayBuffer] { /** Avoid reallocation of buffer if length is known. */ def fromIterable[B](coll: Iterable[B]): ArrayBuffer[B] = if (coll.knownSize >= 0) { - val elems = new Array[AnyRef](coll.knownSize) + val array = new Array[AnyRef](coll.knownSize) val it = coll.iterator() - for (i <- 0 until elems.length) elems(i) = it.next().asInstanceOf[AnyRef] - new ArrayBuffer[B](elems, elems.length) + for (i <- 0 until array.length) array(i) = it.next().asInstanceOf[AnyRef] + new ArrayBuffer[B](array, array.length) } else new ArrayBuffer[B] ++= coll } -class ArrayBufferView[A](val elems: Array[AnyRef], val start: Int, val end: Int) extends IndexedView[A] { - def length = end - start - def apply(n: Int) = elems(start + n).asInstanceOf[A] +class ArrayBufferView[A](val array: Array[AnyRef], val length: Int) extends IndexedView[A] { + def apply(n: Int) = array(n).asInstanceOf[A] override def className = "ArrayBufferView" } + +/** An object used internally by collections backed by an extensible Array[AnyRef] */ +object RefArrayUtils { + + def ensureSize(array: Array[AnyRef], end: Int, n: Int): Array[AnyRef] = { + // Use a Long to prevent overflows + val arrayLength: Long = array.length + def growArray = { + var newSize: Long = arrayLength * 2 + while (n > newSize) + newSize = newSize * 2 + // Clamp newSize to Int.MaxValue + if (newSize > Int.MaxValue) newSize = Int.MaxValue + + val newArray: Array[AnyRef] = new Array(newSize.toInt) + Array.copy(array, 0, newArray, 0, end) + newArray + } + if (n <= arrayLength) array else growArray + } + + /** Remove elements of this array at indices after `sz`. + */ + def nullElems(array: Array[AnyRef], start: Int, end: Int): Unit = { + var i = start + while (i < end) { + array(i) = null + i += 1 + } + } +} diff --git a/src/main/scala/strawman/collection/mutable/ArrayOps.scala b/src/main/scala/strawman/collection/mutable/ArrayOps.scala new file mode 100644 index 0000000000..e959679857 --- /dev/null +++ b/src/main/scala/strawman/collection/mutable/ArrayOps.scala @@ -0,0 +1,43 @@ +package strawman.collection.mutable + +import strawman.collection.{Iterable, IterableOps, SeqMonoTransforms, ArrayLike, IndexedView, TagGuidedPolyTransforms} +import strawman.collection.immutable +import scala.{Int, Array, AnyVal} +import scala.Predef.String +import scala.reflect.ClassTag + +class ArrayOps[A](val xs: Array[A]) + extends AnyVal with IterableOps[A] + with SeqMonoTransforms[A, Array[A]] + with TagGuidedPolyTransforms[A, Array] + with Buildable[A, Array[A]] + with ArrayLike[A] { + + override def view = new ArrayView(xs) + + protected def coll = view + def iterator() = coll.iterator() + + def length = xs.length + def apply(i: Int) = xs.apply(i) + + def elemTag: ClassTag[A] = ClassTag(xs.getClass.getComponentType) + + protected def fromIterableWithSameElemType(coll: Iterable[A]): Array[A] = coll.toArray[A](elemTag) + + def fromIterable[B: ClassTag](coll: Iterable[B]): Array[B] = coll.toArray[B] + + protected[this] def newBuilder = new ArrayBuffer[A].mapResult(_.toArray(elemTag)) + + override def knownSize = xs.length + + override def className = "Array" + + def readOnly: immutable.Array[A] = new immutable.Array(xs) +} + +case class ArrayView[A](xs: Array[A]) extends IndexedView[A] { + def length = xs.length + def apply(n: Int) = xs(n) + override def className = "ArrayView" +} diff --git a/src/main/scala/strawman/collection/mutable/Builder.scala b/src/main/scala/strawman/collection/mutable/Builder.scala index cbd19aa56e..5e76445f26 100644 --- a/src/main/scala/strawman/collection/mutable/Builder.scala +++ b/src/main/scala/strawman/collection/mutable/Builder.scala @@ -1,27 +1,27 @@ package strawman.collection.mutable -import scala.{Boolean, Any, Char} +import scala.{Boolean, Any, Char, Unit} import java.lang.String import strawman.collection.{IterableMonoTransforms, IterableOnce} /** Base trait for collection builders */ -trait Builder[-A, +To] { self => +trait Builder[-A, +To] extends Growable[A] { self => /** Append an element */ def +=(x: A): this.type + /** Clears the contents of this builder. + * After execution of this method the builder will contain no elements. + */ + def clear(): Unit + /** Result collection consisting of all elements appended so far. */ def result: To - /** Bulk append. Can be overridden if specialized implementations are available. */ - def ++=(xs: IterableOnce[A]): this.type = { - xs.iterator().foreach(+=) - this - } - /** A builder resulting from this builder my mapping the result using `f`. */ def mapResult[NewTo](f: To => NewTo) = new Builder[A, NewTo] { def +=(x: A): this.type = { self += x; this } + def clear(): Unit = self.clear() override def ++=(xs: IterableOnce[A]): this.type = { self ++= xs; this } def result: NewTo = f(self.result) } @@ -52,6 +52,8 @@ class StringBuilder extends Builder[Char, String] { def += (x: Char) = { sb.append(x); this } + def clear() = sb.setLength(0) + /** Overloaded version of `++=` that takes a string */ def ++= (s: String) = { sb.append(s); this } diff --git a/src/main/scala/strawman/collection/mutable/Growable.scala b/src/main/scala/strawman/collection/mutable/Growable.scala new file mode 100644 index 0000000000..71b1c639dd --- /dev/null +++ b/src/main/scala/strawman/collection/mutable/Growable.scala @@ -0,0 +1,69 @@ +/* __ *\ +** ________ ___ / / ___ Scala API ** +** / __/ __// _ | / / / _ | (c) 2003-2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | | ** +** |/ ** +\* */ + +package strawman.collection.mutable + +import strawman.collection.IterableOnce +import scala.Unit +import scala.annotation.tailrec +import strawman.collection.{toOldSeq, toNewSeq} + +/** This trait forms part of collections that can be augmented + * using a `+=` operator and that can be cleared of all elements using + * a `clear` method. + * + * @author Martin Odersky + * @version 2.8 + * @since 2.8 + * @define coll growable collection + * @define Coll `Growable` + * @define add add + * @define Add add + */ +trait Growable[-A] { + + /** ${Add}s a single element to this $coll. + * + * @param elem the element to $add. + * @return the $coll itself + */ + def +=(elem: A): this.type + + /** ${Add}s two or more elements to this $coll. + * + * @param elem1 the first element to $add. + * @param elem2 the second element to $add. + * @param elems the remaining elements to $add. + * @return the $coll itself + */ + def +=(elem1: A, elem2: A, elems: A*): this.type = this += elem1 += elem2 ++= (elems.toStrawman: IterableOnce[A]) + + /** ${Add}s all elements produced by a TraversableOnce to this $coll. + * + * @param xs the TraversableOnce producing the elements to $add. + * @return the $coll itself. + */ + def ++=(xs: IterableOnce[A]): this.type = { + @tailrec def loop(xs: scala.collection.LinearSeq[A]): Unit = { + if (xs.nonEmpty) { + this += xs.head + loop(xs.tail) + } + } + xs match { + case xs: scala.collection.LinearSeq[_] => loop(xs.asInstanceOf[scala.collection.LinearSeq[A]]) + case xs => xs.iterator() foreach += // Deviation: IterableOnce does not define `foreach`. + } + this + } + + /** Clears the $coll's contents. After this operation, the + * $coll is empty. + */ + def clear(): Unit +} diff --git a/src/main/scala/strawman/collection/mutable/ListBuffer.scala b/src/main/scala/strawman/collection/mutable/ListBuffer.scala index bf7fa1eca6..e348155cdb 100644 --- a/src/main/scala/strawman/collection/mutable/ListBuffer.scala +++ b/src/main/scala/strawman/collection/mutable/ListBuffer.scala @@ -1,8 +1,13 @@ package strawman.collection.mutable -import scala.{Int, Unit} -import strawman.collection.{SeqLike, IterableFactory, Iterable, Seq} +import scala.{Int, Unit, Boolean} +import scala.Int._ +import strawman.collection +import strawman.collection.{Iterator, Iterable, IterableOnce, IterableFactory, SeqLike} import strawman.collection.immutable.{List, Nil, ::} +import scala.annotation.tailrec +import java.lang.IndexOutOfBoundsException +import scala.Predef.{assert, intWrapper} /** Concrete collection type: ListBuffer */ class ListBuffer[A] @@ -11,10 +16,13 @@ class ListBuffer[A] with Buildable[A, ListBuffer[A]] with Builder[A, ListBuffer[A]] { - private var first, last: List[A] = Nil + private var first: List[A] = Nil + private var last: ::[A] = null private var aliased = false private var len = 0 + private type Predecessor[A] = ::[A] /*| Null*/ + def iterator() = first.iterator() def fromIterable[B](coll: Iterable[B]) = ListBuffer.fromIterable(coll) @@ -33,24 +41,158 @@ class ListBuffer[A] aliased = false } + private def ensureUnaliased() = if (aliased) copyElems() + /** Convert to list; avoids copying where possible. */ def toList = { aliased = true first } + def clear(): Unit = { + first = Nil + } + def +=(elem: A) = { - if (aliased) copyElems() - val last1 = elem :: Nil - last match { - case last: ::[A] => last.next = last1 - case _ => first = last1 - } + ensureUnaliased() + val last1 = (elem :: Nil).asInstanceOf[::[A]] + if (len == 0) first = last1 else last.next = last1 last = last1 len += 1 this } + private def locate(i: Int): Predecessor[A] = + if (i == 0) null + else if (i == len) last + else { + assert(i > 0 && i < len) + var j = i - 1 + var p = first + while (j > 0) { + p = p.tail + j -= 1 + } + p.asInstanceOf[Predecessor[A]] + } + + private def getNext(p: Predecessor[A]): List[A] = + if (p == null) first else p.next + + private def setNext(p: Predecessor[A], nx: List[A]): Unit = + if (p == null) first = nx else p.next = nx + + def update(idx: Int, elem: A): Unit = { + ensureUnaliased() + if (idx < 0 || idx >= len) throw new IndexOutOfBoundsException + val p = locate(idx) + setNext(p, elem :: getNext(p).tail) + } + + def insert(idx: Int, elem: A): Unit = { + ensureUnaliased() + if (idx < 0 || idx > len) throw new IndexOutOfBoundsException + if (idx == len) +=(elem) + else { + val p = locate(idx) + setNext(p, elem :: getNext(p)) + len += 1 + } + } + + private def insertAfter(p: Predecessor[A], it: Iterator[A]) = { + var prev = p + val follow = getNext(prev) + while (it.hasNext) { + len += 1 + val next = (it.next :: follow).asInstanceOf[::[A]] + setNext(prev, next) + prev = next + } + } + + def insertAll(idx: Int, elems: IterableOnce[A]): Unit = { + ensureUnaliased() + val it = elems.iterator() + if (it.hasNext) { + ensureUnaliased() + if (idx < 0 || idx > len) throw new IndexOutOfBoundsException + if (idx == len) ++=(elems) + else insertAfter(locate(idx), it) + } + } + + def remove(idx: Int): A = { + ensureUnaliased() + if (idx < 0 || idx >= len) throw new IndexOutOfBoundsException + len -= 1 + val p = locate(idx) + val nx = getNext(p) + setNext(p, nx.tail) + nx.head + } + + def remove(idx: Int, n: Int): Unit = + if (n > 0) { + ensureUnaliased() + if (idx < 0 || idx + n > len) throw new IndexOutOfBoundsException + removeAfter(locate(idx), n) + } + + private def removeAfter(prev: Predecessor[A], n: Int) = { + @tailrec def ahead(p: List[A], n: Int): List[A] = + if (n == 0) p else ahead(p.tail, n - 1) + setNext(prev, ahead(getNext(prev), n)) + len -= n + } + + def mapInPlace(f: A => A): this.type = { + ensureUnaliased() + val buf = new ListBuffer[A] + for (elem <- this) buf += f(elem) + first = buf.first + last = buf.last + this + } + + def flatMapInPlace(f: A => IterableOnce[A]): this.type = { + ensureUnaliased() + val prev: Predecessor[A] = null + var cur: List[A] = first + while (!cur.isEmpty) { + val follow = cur.tail + setNext(prev, follow) + len -= 1 + insertAfter(prev, f(cur.head).iterator()) + cur = follow + } + this + } + + def filterInPlace(p: A => Boolean): this.type = { + ensureUnaliased() + var prev: Predecessor[A] = null + var cur: List[A] = first + while (!cur.isEmpty) { + val follow = cur.tail + if (!p(cur.head)) { + setNext(prev, follow) + len -= 1 + } + prev = cur.asInstanceOf[Predecessor[A]] + cur = follow + } + this + } + + def patchInPlace(from: Int, patch: collection.Seq[A], replaced: Int): this.type = { + ensureUnaliased() + val p = locate(from) + removeAfter(p, replaced `min` length - from) + insertAfter(p, patch.iterator()) + this + } + def result = this override def className = "ListBuffer" diff --git a/src/main/scala/strawman/collection/mutable/Seq.scala b/src/main/scala/strawman/collection/mutable/Seq.scala new file mode 100644 index 0000000000..ce054d3aa4 --- /dev/null +++ b/src/main/scala/strawman/collection/mutable/Seq.scala @@ -0,0 +1,87 @@ +package strawman.collection.mutable + +import java.lang.IndexOutOfBoundsException +import scala.{Int, Long, Unit, Boolean, Array} +import strawman.collection +import strawman.collection.{IterableOnce, toNewSeq, toOldSeq} +import scala.Predef.intWrapper + +trait Seq[A] extends strawman.collection.Seq[A] { + def update(idx: Int, elem: A): Unit + def mapInPlace(f: A => A): this.type +} + +trait GrowableSeq[A] extends Seq[A] with Growable[A] { + def insert(idx: Int, elem: A): Unit + def insertAll(idx: Int, elems: IterableOnce[A]): Unit + def remove(idx: Int): A + def remove(from: Int, n: Int): Unit + def flatMapInPlace(f: A => IterableOnce[A]): this.type + def filterInPlace(p: A => Boolean): this.type + def patchInPlace(from: Int, patch: collection.Seq[A], replaced: Int): this.type + + // +=, ++=, clear inherited from Growable + def +=:(elem: A): this.type = { insert(0, elem); this } + def +=:(elem1: A, elem2: A, elems: A*): this.type = elem1 +=: elem2 +=: elems.toStrawman ++=: this + def ++=:(elems: IterableOnce[A]): this.type = { insertAll(0, elems); this } + + def dropInPlace(n: Int): this.type = { remove(0, n); this } + def dropRightInPlace(n: Int): this.type = { remove(length - n, n); this } + def takeInPlace(n: Int): this.type = { remove(n, length); this } + def takeRightInPlace(n: Int): this.type = { remove(0, length - n); this } + def sliceInPlace(start: Int, end: Int): this.type = takeInPlace(end).dropInPlace(start) + + def dropWhileInPlace(p: A => Boolean): this.type = { + val idx = indexWhere(!p(_)) + if (idx < 0) { clear(); this } else dropInPlace(idx) + } + def takeWhileInPlace(p: A => Boolean): this.type = { + val idx = indexWhere(!p(_)) + if (idx < 0) this else takeInPlace(idx) + } + def padToInPlace(len: Int, elem: A): this.type = { + while (length < len) +=(elem) + this + } +} + +trait IndexedOptimizedSeq[A] extends Seq[A] { + def mapInPlace(f: A => A): this.type = { + var i = 0 + while (i < size) { this(i) = f(this(i)); i += 1 } + this + } +} + +trait IndexedOptimizedGrowableSeq[A] extends IndexedOptimizedSeq[A] with GrowableSeq[A] { + def flatMapInPlace(f: A => IterableOnce[A]): this.type = { + var i = 0 + val newElemss = new Array[IterableOnce[A]](size) + while (i < size) { newElemss(i) = f(this(i)); i += 1 } + clear() + i = 0 + while (i < size) { ++=(newElemss(i)); i += 1 } + this + } + def filterInPlace(p: A => Boolean): this.type = { + var i = 0 + var j = 0 + while (i < size) { + if (p(apply(i))) { + this(j) = this(i) + j += 1 + } + i += 1 + } + takeInPlace(j) + } + def patchInPlace(from: Int, patch: collection.Seq[A], replaced: Int): this.type = { + val n = patch.length min replaced + var i = 0 + while (i < n) { update(from + i, patch(i)); i += 1 } + if (i < patch.length) insertAll(from + i, patch.iterator().drop(i)) + else if (i < replaced) remove(from + i, replaced - i) + this + } +} + diff --git a/src/main/scala/strawman/collection/mutable/Set.scala b/src/main/scala/strawman/collection/mutable/Set.scala new file mode 100644 index 0000000000..784132d47d --- /dev/null +++ b/src/main/scala/strawman/collection/mutable/Set.scala @@ -0,0 +1,62 @@ +package strawman.collection.mutable + +import strawman.collection +import strawman.collection.IterableOnce +import scala.{Int, Boolean, Unit, Option, Some, None} +import scala.Predef.??? + +trait Set[A] extends collection.Set[A] with Growable[A] { + + def +=(elem: A): this.type + def -=(elem: A): this.type + + def contains(elem: A): Boolean + def get(elem: A): Option[A] + + def insert(elem: A): Boolean = + !contains(elem) && { +=(elem); true } + + def remove(elem: A): Option[A] = { + val res = get(elem) + -=(elem) + res + } + + def mapInPlace(f: A => A): Unit = { + val toAdd = Set[A]() + val toRemove = Set[A]() + for (elem <- this) { + val mapped = f(elem) + if (!contains(mapped)) { + toAdd += mapped + toRemove -= elem + } + } + for (elem <- toRemove) +=(elem) + for (elem <- toAdd) -=(elem) + } + + def flatMapInPlace(f: A => IterableOnce[A]): Unit = { + val toAdd = Set[A]() + val toRemove = Set[A]() + for (elem <- this) + for (mapped <- f(elem).iterator()) + if (!contains(mapped)) { + toAdd += mapped + toRemove -= elem + } + for (elem <- toRemove) -=(elem) + for (elem <- toAdd) +=(elem) + } + + def filterInPlace(p: A => Boolean): Unit = { + val toRemove = Set[A]() + for (elem <- this) + if (!p(elem)) toRemove += elem + for (elem <- toRemove) + -=(elem) + } +} +object Set { + def apply[A](xs: A*): Set[A] = ??? +} diff --git a/src/main/scala/strawman/collection/package.scala b/src/main/scala/strawman/collection/package.scala index ad5664fc5e..d55340543b 100644 --- a/src/main/scala/strawman/collection/package.scala +++ b/src/main/scala/strawman/collection/package.scala @@ -3,8 +3,10 @@ package strawman import scala.{AnyVal, Array, Char, Int, Unit} import scala.Predef.String import scala.reflect.ClassTag -import strawman.collection.immutable.List -import strawman.collection.mutable.{ArrayBuffer, Buildable} +import scala.language.implicitConversions +import strawman.collection.mutable.{ArrayOps, ArrayView} +import strawman.collection.immutable.{StringOps, StringView} +import strawman.collection.immutable /** A strawman architecture for new collections. It contains some * example collection classes and methods with the intent to expose @@ -84,23 +86,52 @@ import strawman.collection.mutable.{ArrayBuffer, Buildable} * map, flatMap, ++, zip */ package object collection extends LowPriority { - import scala.language.implicitConversions + // ------------------ Decorators to add collection ops to existing types ----------------------- /** Decorator to add collection operations to strings. */ implicit def stringToStringOps(s: String): StringOps = new StringOps(s) - /** Decorator to add collection operations to arrays. */ + /** Decorator to add collection operations to arrays. + * Note: should be specialized for efficiency. + */ implicit def arrayToArrayOps[A](as: Array[A]): ArrayOps[A] = new ArrayOps[A](as) + + implicit def immutableArrayToArrayOps[A](as: immutable.Array[A]): immutable.ArrayOps[A] = new immutable.ArrayOps[A](as) + + implicit class toNewIterator[A](val it: scala.Iterator[A]) extends AnyVal { + def toStrawman = new strawman.collection.Iterator[A] { + def hasNext = it.hasNext + def next() = it.next() + } + } + + implicit class toOldIterator[A](val it: strawman.collection.Iterator[A]) extends AnyVal { + def toClassic = new scala.Iterator[A] { + def hasNext = it.hasNext + def next() = it.next() + } + } + + implicit class toNewSeq[A](val s: scala.collection.Seq[A]) extends AnyVal { + def toStrawman: strawman.collection.Seq[A] = + new strawman.collection.mutable.ArrayBuffer() ++= s.iterator.toStrawman + } + + implicit class toOldSeq[A](val s: strawman.collection.Seq[A]) extends AnyVal { + def toClassic: scala.collection.Seq[A] = + new scala.collection.mutable.ArrayBuffer ++= s.iterator().toClassic + } } class LowPriority { - import scala.language.implicitConversions - import strawman.collection._ /** Convert array to iterable via view. Lower priority than ArrayOps */ implicit def arrayToView[T](xs: Array[T]): ArrayView[T] = new ArrayView[T](xs) + /** Convert immutable array to iterable via view. Lower priority than ArrayOps */ + implicit def immutableArrayToView[T](xs: immutable.Array[T]): immutable.ArrayView[T] = new immutable.ArrayView[T](xs) + /** Convert string to iterable via view. Lower priority than StringOps */ implicit def stringToView(s: String): StringView = new StringView(s) } diff --git a/src/test/scala/strawman/collection/test/Test.scala b/src/test/scala/strawman/collection/test/Test.scala index 5b7922a982..0bc0eefb6d 100644 --- a/src/test/scala/strawman/collection/test/Test.scala +++ b/src/test/scala/strawman/collection/test/Test.scala @@ -4,9 +4,9 @@ import java.lang.String import scala.{Int, Unit, Array, StringContext, Boolean, Any, Char} import scala.Predef.{println, charWrapper} -import strawman.collection._ import strawman.collection.immutable._ import strawman.collection.mutable._ +import strawman.collection.{Seq, View, _} import org.junit.Test class StrawmanTest {