From 848f5d5cccfed80f0357694a17cd257c9e27eec7 Mon Sep 17 00:00:00 2001 From: Stefan Zeiger Date: Tue, 29 Aug 2017 17:01:52 +0200 Subject: [PATCH] Primitive array types in ArrayOps / ArrayBuilder / ImmutableArray This is similar to the old implementation. Primitive array types are not polymorphic on the JVM so we need to special-case them in order to be able to efficiently wrap existing arrays as collections. We also remove ArrayView in favor of WrappedArray and ImmutableArray. With the new equality semantics (where Views are never equal unless they are the same reference) using a View as a default wrapper type for Array is problematic. ImmutableArray is basically the same as ArrayView, except it is an immutable.Seq and WrappedArray is the same for mutable.Seq, so we get the expected equality semantics and strict operations for both of them. Implicit conversions from Array to Seq always produce a WrappedArray. This is the safe choice because there is no guarantee that the array is effectively immutable. ImmutableArray is intended for use in compiler-generated varargs bridges and manually by the user via an explicit conversion call. Unlike the 2.12 collections the strawman currently requires a mutable Seq to be Growable and Shrinkable. This needs to be changed in order to implement WrappedArray. The new definition of mutable.Seq allows for fixed-size collections like WrappedArray. A mutable.Seq which is growable and shrinkable is a mutable.Buffer. We also introduce mutable.IndexedSeq as a new type. It does not define any new operations of its own but merely combines mutable.Seq with collection.IndexedSeq for orthogonality with immutable.IndexedSeq. --- .../immutable/ArrayBaselineBenchmark.scala | 1 - .../immutable/ArrayViewBenchmark.scala | 107 ---- .../scala/strawman/collection/ArrayOps.scala | 55 +- .../collection/StrictOptimizedSeqOps.scala | 5 +- .../collection/immutable/ImmutableArray.scala | 185 +++++- .../collection/mutable/ArrayBuffer.scala | 4 +- .../collection/mutable/ArrayBuilder.scala | 537 ++++++++++++++++++ .../strawman/collection/mutable/Buffer.scala | 109 +++- .../collection/mutable/IndexedSeq.scala | 10 + .../collection/mutable/Iterable.scala | 8 +- .../strawman/collection/mutable/Map.scala | 1 + .../strawman/collection/mutable/Seq.scala | 100 +--- .../strawman/collection/mutable/Set.scala | 1 + .../collection/mutable/WrappedArray.scala | 242 ++++++++ .../scala/strawman/collection/package.scala | 4 +- 15 files changed, 1101 insertions(+), 268 deletions(-) delete mode 100644 benchmarks/time/src/main/scala/strawman/collection/immutable/ArrayViewBenchmark.scala create mode 100644 collections/src/main/scala/strawman/collection/mutable/ArrayBuilder.scala create mode 100644 collections/src/main/scala/strawman/collection/mutable/IndexedSeq.scala create mode 100644 collections/src/main/scala/strawman/collection/mutable/WrappedArray.scala diff --git a/benchmarks/time/src/main/scala/strawman/collection/immutable/ArrayBaselineBenchmark.scala b/benchmarks/time/src/main/scala/strawman/collection/immutable/ArrayBaselineBenchmark.scala index 0192f7ed3c..e58ae8534c 100644 --- a/benchmarks/time/src/main/scala/strawman/collection/immutable/ArrayBaselineBenchmark.scala +++ b/benchmarks/time/src/main/scala/strawman/collection/immutable/ArrayBaselineBenchmark.scala @@ -4,7 +4,6 @@ import java.util.concurrent.TimeUnit import org.openjdk.jmh.annotations._ import org.openjdk.jmh.infra.Blackhole -import strawman.collection.{ArrayView, View} import scala.{Any, AnyRef, Int, Long, Unit, Array} import scala.Predef.intWrapper diff --git a/benchmarks/time/src/main/scala/strawman/collection/immutable/ArrayViewBenchmark.scala b/benchmarks/time/src/main/scala/strawman/collection/immutable/ArrayViewBenchmark.scala deleted file mode 100644 index 242c93e5d2..0000000000 --- a/benchmarks/time/src/main/scala/strawman/collection/immutable/ArrayViewBenchmark.scala +++ /dev/null @@ -1,107 +0,0 @@ -package strawman.collection.immutable - -import java.util.concurrent.TimeUnit - -import org.openjdk.jmh.annotations._ -import org.openjdk.jmh.infra.Blackhole -import strawman.collection.{ArrayView, View} -import scala.{Any, AnyRef, Int, Long, Unit, Array} -import scala.Predef.intWrapper - -@BenchmarkMode(scala.Array(Mode.AverageTime)) -@OutputTimeUnit(TimeUnit.NANOSECONDS) -@Fork(15) -@Warmup(iterations = 30) -@Measurement(iterations = 15) -@State(Scope.Benchmark) -class ArrayViewBenchmark { - - @Param(scala.Array("39", "282", "73121", "7312102")) - var size: Int = _ - - @Param(scala.Array("39")) - var vLoSize: Int = _ - - var shortRangingFactor : Int = (size * 0.2).toInt - - var v: View[Long] = _ - var vLo: View[Long] = _ - - def fillArray(range: Int) = { - val array = new Array[Long](range) - var i = 0 - while (i < range) { - array(i) = scala.util.Random.nextInt(size).toLong - i += 1 - } - array - } - - @Setup(Level.Trial) - def initTrial(): Unit = { - - v = ArrayView(fillArray(size)) - vLo = ArrayView(fillArray(vLoSize)) - } - - @Benchmark - def sum (bh: Blackhole) = { - val ret : Long = v.foldLeft(0L)(_+_) - bh.consume(ret) - } - - @Benchmark - def sumOfSquares (bh: Blackhole) = { - val ret : Long = v - .map(d => d * d) - .foldLeft(0L)(_+_) - bh.consume(ret) - } - - @Benchmark - def sumOfSquaresEven (bh: Blackhole) = { - val ret : Long = v - .filter(x => x % 2L == 0L) - .map(x => x * x) - .foldLeft(0L)(_+_) - bh.consume(ret) - } - - @Benchmark - def maps (bh: Blackhole) = { - val ret : Long = v - .map(x => x + (x & 0xD) + 0xCAFED00D) - .map(x => x + (x & 0xE) + 0xD15EA5E) - .map(x => x + (x & 0xA) + 0xDABBAD00) - .foldLeft(0L)(_+_) - bh.consume(ret) - } - - @Benchmark - def filters (bh: Blackhole) = { - val ret : Long = v - .filter(x => (x & 0x13) != 0x11) - .filter(x => (x & 0x12) == 0x12) - .filter(x => (x & 0x11) != 0x10) - .foldLeft(0L)(_+_) - bh.consume(ret) - } - - @Benchmark - def cart (bh: Blackhole) = { - val ret : Long = v - .flatMap(d => vLo.map (dp => dp * d)) - .foldLeft(0L)(_+_) - bh.consume(ret) - } - - @Benchmark - def flatMap_take (bh: Blackhole) = { - val ret = v - .flatMap((x) => vLo - .map((dP) => dP * x)) - .take(shortRangingFactor) - .foldLeft(0L)(_+_) - bh.consume(ret) - } -} diff --git a/collections/src/main/scala/strawman/collection/ArrayOps.scala b/collections/src/main/scala/strawman/collection/ArrayOps.scala index 05116107ff..408e308ea6 100644 --- a/collections/src/main/scala/strawman/collection/ArrayOps.scala +++ b/collections/src/main/scala/strawman/collection/ArrayOps.scala @@ -1,19 +1,35 @@ package strawman package collection -import scala.{AnyVal, Array, ArrayIndexOutOfBoundsException, Char, Int, throws} +import scala.{AnyVal, Array, ArrayIndexOutOfBoundsException, Char, Int, throws, Boolean, Serializable, Unit, `inline`} +import scala.Predef.??? import mutable.{ArrayBuffer, GrowableBuilder} - +import immutable.ImmutableArray import scala.reflect.ClassTag -class ArrayOps[A](val xs: Array[A]) - extends AnyVal - with IterableOnce[A] - with IndexedSeqOps[A, immutable.IndexedSeq, Array[A]] - with StrictOptimizedIterableOps[A, Seq, Array[A]] - with ArrayLike[A] { +object ArrayOps { + class WithFilter[A](p: A => Boolean, ao: ArrayOps[A]) extends collection.WithFilter[A, immutable.IndexedSeq] { + protected[this] def filtered = View.Filter(ao.toIterable, p, isFlipped = false) + def map[B](f: A => B): immutable.IndexedSeq[B] = ao.iterableFactory.from(View.Map(filtered, f)) + def flatMap[B](f: A => IterableOnce[B]): immutable.IndexedSeq[B] = ao.iterableFactory.from(View.FlatMap(filtered, f)) + def foreach[U](f: A => U): Unit = filtered.foreach(f) + def map[B: ClassTag](f: A => B): Array[B] = ao.fromTaggedIterable(View.Map(filtered, f)) + def flatMap[B: ClassTag](f: A => IterableOnce[B]): Array[B] = ao.fromTaggedIterable(View.FlatMap(filtered, f)) + def withFilter(q: A => Boolean): WithFilter[A] = new WithFilter[A](a => p(a) && q(a), ao) + } +} + +class ArrayOps[A](val xs: Array[A]) extends AnyVal + with IterableOnce[A] + with IndexedSeqOps[A, immutable.IndexedSeq, Array[A]] + with StrictOptimizedSeqOps[A, Seq, Array[A]] + with ArrayLike[A] { + + protected def fromTaggedIterable[B: ClassTag](coll: Iterable[B]): Array[B] = coll.toArray[B] + + override def withFilter(p: A => Boolean): ArrayOps.WithFilter[A] = new ArrayOps.WithFilter[A](p, this) - def toIterable = ArrayView(xs) + def toIterable: IndexedSeq[A] = ImmutableArray.unsafeWrapArray(xs) protected[this] def coll: Array[A] = xs override def toSeq: immutable.Seq[A] = fromIterable(toIterable) @@ -21,13 +37,10 @@ class ArrayOps[A](val xs: Array[A]) @throws[ArrayIndexOutOfBoundsException] def apply(i: Int) = xs.apply(i) - override def view = ArrayView(xs) - def elemTag: ClassTag[A] = ClassTag(xs.getClass.getComponentType) def iterableFactory = immutable.IndexedSeq - protected[this] def fromTaggedIterable[B: ClassTag](coll: Iterable[B]): Array[B] = coll.toArray[B] protected[this] def fromSpecificIterable(coll: Iterable[A]): Array[A] = coll.toArray[A](elemTag) protected[this] def newSpecificBuilder() = ArrayBuffer.newBuilder[A]().mapResult(_.toArray(elemTag)) @@ -47,15 +60,17 @@ class ArrayOps[A](val xs: Array[A]) def flatMap[B: ClassTag](f: A => IterableOnce[B]): Array[B] = fromTaggedIterable(View.FlatMap(toIterable, f)) - def ++[B >: A : ClassTag](xs: Iterable[B]): Array[B] = fromTaggedIterable(View.Concat(toIterable, xs)) + @`inline` final def ++[B >: A : ClassTag](xs: Iterable[B]): Array[B] = appendedAll(xs) def zip[B: ClassTag](that: Iterable[B]): Array[(A, B)] = fromTaggedIterable(View.Zip(toIterable, that)) -} - -case class ArrayView[A](xs: Array[A]) extends IndexedView[A] { - def length = xs.length - @throws[ArrayIndexOutOfBoundsException] - def apply(n: Int) = xs(n) - override def className = "ArrayView" + def appended[B >: A : ClassTag](x: B): Array[B] = fromTaggedIterable(View.Append(toIterable, x)) + @`inline` final def :+ [B >: A : ClassTag](x: B): Array[B] = appended(x) + def prepended[B >: A : ClassTag](x: B): Array[B] = fromTaggedIterable(View.Prepend(x, toIterable)) + @`inline` final def +: [B >: A : ClassTag](x: B): Array[B] = prepended(x) + def prependedAll[B >: A : ClassTag](prefix: Iterable[B]): Array[B] = fromTaggedIterable(View.Concat(prefix, toIterable)) + @`inline` final def ++: [B >: A : ClassTag](prefix: Iterable[B]): Array[B] = prependedAll(prefix) + def appendedAll[B >: A : ClassTag](suffix: Iterable[B]): Array[B] = fromTaggedIterable(View.Concat(toIterable, suffix)) + @`inline` final def :++ [B >: A : ClassTag](suffix: Iterable[B]): Array[B] = appendedAll(suffix) + @`inline` final def concat[B >: A : ClassTag](suffix: Iterable[B]): Array[B] = appendedAll(suffix) } diff --git a/collections/src/main/scala/strawman/collection/StrictOptimizedSeqOps.scala b/collections/src/main/scala/strawman/collection/StrictOptimizedSeqOps.scala index a8163bad1e..a48dbabc64 100644 --- a/collections/src/main/scala/strawman/collection/StrictOptimizedSeqOps.scala +++ b/collections/src/main/scala/strawman/collection/StrictOptimizedSeqOps.scala @@ -1,13 +1,14 @@ package strawman.collection -import scala.{Int, math} +import scala.{Int, math, Any} /** * Trait that overrides operations on sequences in order * to take advantage of strict builders. */ trait StrictOptimizedSeqOps [+A, +CC[_], +C] - extends SeqOps[A, CC, C] + extends Any + with SeqOps[A, CC, C] with StrictOptimizedIterableOps[A, CC, C] { override def distinctBy[B](f: A => B): C = { diff --git a/collections/src/main/scala/strawman/collection/immutable/ImmutableArray.scala b/collections/src/main/scala/strawman/collection/immutable/ImmutableArray.scala index 9a910727eb..b8fcc1ad60 100644 --- a/collections/src/main/scala/strawman/collection/immutable/ImmutableArray.scala +++ b/collections/src/main/scala/strawman/collection/immutable/ImmutableArray.scala @@ -4,9 +4,12 @@ package collection.immutable import strawman.collection.mutable.{ArrayBuffer, Builder} import strawman.collection.{IterableOnce, Iterator, SeqFactory, StrictOptimizedSeqFactory, View} -import scala.{Any, ArrayIndexOutOfBoundsException, Boolean, Int, Nothing, UnsupportedOperationException, throws} +import scala.{Any, ArrayIndexOutOfBoundsException, Boolean, Int, Nothing, UnsupportedOperationException, throws, Array, AnyRef, `inline`, Serializable, Byte, Short, Long, Double, Unit, Float, Char} +import scala.annotation.unchecked.uncheckedVariance +import scala.util.hashing.MurmurHash3 import scala.runtime.ScalaRunTime import scala.Predef.intWrapper +import java.util.Arrays /** * An immutable array. @@ -16,27 +19,31 @@ import scala.Predef.intWrapper * @define coll immutable array * @define Coll `ImmutableArray` */ -class ImmutableArray[+A] private[collection] (private val elements: scala.Array[Any]) +sealed abstract class ImmutableArray[+A] extends IndexedSeq[A] with IndexedSeqOps[A, ImmutableArray, ImmutableArray[A]] with StrictOptimizedSeqOps[A, ImmutableArray, ImmutableArray[A]] { def iterableFactory: SeqFactory[ImmutableArray] = ImmutableArray + /** The wrapped mutable `Array` that backs this `ImmutableArray`. Any changes to this array will break + * the expected immutability. */ + def unsafeArray: Array[A @uncheckedVariance] + // uncheckedVariance should be safe: Array[A] for reference types A is covariant at the JVM level. Array[A] for + // primitive types A can only be widened to Array[Any] which erases to Object. + protected[this] def fromSpecificIterable(coll: strawman.collection.Iterable[A]): ImmutableArray[A] = fromIterable(coll) protected[this] def newSpecificBuilder(): Builder[A, ImmutableArray[A]] = ImmutableArray.newBuilder[A]() - def length: Int = elements.length - @throws[ArrayIndexOutOfBoundsException] - def apply(i: Int): A = ScalaRunTime.array_apply(elements, i).asInstanceOf[A] + def apply(i: Int): A override def updated[B >: A](index: Int, elem: B): ImmutableArray[B] = { val dest = scala.Array.ofDim[Any](length) - java.lang.System.arraycopy(elements, 0, dest, 0, length) + scala.Array.copy(unsafeArray, 0, dest, 0, length) dest(index) = elem - new ImmutableArray(dest) + ImmutableArray.unsafeWrapArray(dest) } override def map[B](f: A => B): ImmutableArray[B] = ImmutableArray.tabulate(length)(i => f(apply(i))) @@ -44,24 +51,24 @@ class ImmutableArray[+A] private[collection] (private val elements: scala.Array[ override def prepended[B >: A](elem: B): ImmutableArray[B] = { val dest = scala.Array.ofDim[Any](length + 1) dest(0) = elem - java.lang.System.arraycopy(elements, 0, dest, 1, length) - new ImmutableArray(dest) + scala.Array.copy(unsafeArray, 0, dest, 1, length) + ImmutableArray.unsafeWrapArray(dest) } override def appended[B >: A](elem: B): ImmutableArray[B] = { val dest = scala.Array.ofDim[Any](length + 1) - java.lang.System.arraycopy(elements, 0, dest, 0, length) + scala.Array.copy(unsafeArray, 0, dest, 0, length) dest(length) = elem - new ImmutableArray(dest) + ImmutableArray.unsafeWrapArray(dest) } override def appendedAll[B >: A](xs: collection.Iterable[B]): ImmutableArray[B] = xs match { case bs: ImmutableArray[B] => val dest = scala.Array.ofDim[Any](length + bs.length) - java.lang.System.arraycopy(elements, 0, dest, 0, length) - java.lang.System.arraycopy(bs.elements, 0, dest, length, bs.length) - new ImmutableArray(dest) + scala.Array.copy(unsafeArray, 0, dest, 0, length) + scala.Array.copy(bs.unsafeArray, 0, dest, length, bs.length) + ImmutableArray.unsafeWrapArray(dest) case _ => fromIterable(View.Concat(toIterable, xs)) } @@ -70,9 +77,9 @@ class ImmutableArray[+A] private[collection] (private val elements: scala.Array[ xs match { case bs: ImmutableArray[B] => val dest = scala.Array.ofDim[Any](length + bs.length) - java.lang.System.arraycopy(bs.elements, 0, dest, 0, bs.length) - java.lang.System.arraycopy(elements, 0, dest, bs.length, length) - new ImmutableArray(dest) + java.lang.System.arraycopy(bs.unsafeArray, 0, dest, 0, bs.length) + java.lang.System.arraycopy(unsafeArray, 0, dest, bs.length, length) + ImmutableArray.unsafeWrapArray(dest) case _ => fromIterable(View.Concat(xs, toIterable)) } @@ -103,12 +110,13 @@ class ImmutableArray[+A] private[collection] (private val elements: scala.Array[ override def tail: ImmutableArray[A] = if (length > 0) { val dest = scala.Array.ofDim[Any](length - 1) - java.lang.System.arraycopy(elements, 1, dest, 0, length - 1) - new ImmutableArray(dest) + java.lang.System.arraycopy(unsafeArray, 1, dest, 0, length - 1) + ImmutableArray.unsafeWrapArray(dest) } else throw new UnsupportedOperationException("tail of empty array") override def reverse: ImmutableArray[A] = ImmutableArray.tabulate(length)(i => apply(length - 1 - i)) + override def className = "ImmutableArray" } /** @@ -118,16 +126,16 @@ class ImmutableArray[+A] private[collection] (private val elements: scala.Array[ */ object ImmutableArray extends StrictOptimizedSeqFactory[ImmutableArray] { - private[this] lazy val emptyImpl = new ImmutableArray[Nothing](new scala.Array[Any](0)) + private[this] lazy val emptyImpl = new ImmutableArray.ofRef[Nothing](new scala.Array[Nothing](0)) def empty[A]: ImmutableArray[A] = emptyImpl def fromArrayBuffer[A](arr: ArrayBuffer[A]): ImmutableArray[A] = - new ImmutableArray[A](arr.asInstanceOf[ArrayBuffer[Any]].toArray) + unsafeWrapArray[A](arr.asInstanceOf[ArrayBuffer[Any]].toArray) - def from[A](it: strawman.collection.IterableOnce[A]): ImmutableArray[A] = - if (it.knownSize > -1) { - val n = it.knownSize + def from[A](it: strawman.collection.IterableOnce[A]): ImmutableArray[A] = { + val n = it.knownSize + if (n > -1) { val elements = scala.Array.ofDim[Any](n) val iterator = it.iterator() var i = 0 @@ -135,8 +143,9 @@ object ImmutableArray extends StrictOptimizedSeqFactory[ImmutableArray] { ScalaRunTime.array_update(elements, i, iterator.next()) i = i + 1 } - new ImmutableArray(elements) + unsafeWrapArray(elements) } else fromArrayBuffer(ArrayBuffer.from(it)) + } def newBuilder[A](): Builder[A, ImmutableArray[A]] = ArrayBuffer.newBuilder[A]().mapResult(fromArrayBuffer) @@ -150,7 +159,131 @@ object ImmutableArray extends StrictOptimizedSeqFactory[ImmutableArray] { ScalaRunTime.array_update(elements, i, f(i)) i = i + 1 } - new ImmutableArray(elements) + ImmutableArray.unsafeWrapArray(elements) + } + + /** Wrap an existing `Array` into an `ImmutableArray` of the proper primitive specialization type without copying. */ + def unsafeWrapArray[T](x: Array[_]): ImmutableArray[T] = (x match { + case null => null + case x: Array[AnyRef] => new ofRef[AnyRef](x) + case x: Array[Int] => new ofInt(x) + case x: Array[Double] => new ofDouble(x) + case x: Array[Long] => new ofLong(x) + case x: Array[Float] => new ofFloat(x) + case x: Array[Char] => new ofChar(x) + case x: Array[Byte] => new ofByte(x) + case x: Array[Short] => new ofShort(x) + case x: Array[Boolean] => new ofBoolean(x) + case x: Array[Unit] => new ofUnit(x) + }).asInstanceOf[ImmutableArray[T]] + + final class ofRef[T <: AnyRef](val unsafeArray: Array[T]) extends ImmutableArray[T] with Serializable { + def length: Int = unsafeArray.length + @throws[ArrayIndexOutOfBoundsException] + def apply(i: Int): T = unsafeArray(i) + override def hashCode = MurmurHash3.arrayHash(unsafeArray) + override def equals(that: Any) = that match { + case that: ofRef[_] => Arrays.equals(unsafeArray.asInstanceOf[Array[AnyRef]], that.unsafeArray.asInstanceOf[Array[AnyRef]]) + case _ => super.equals(that) + } + } + + final class ofByte(val unsafeArray: Array[Byte]) extends ImmutableArray[Byte] with Serializable { + def length: Int = unsafeArray.length + @throws[ArrayIndexOutOfBoundsException] + def apply(i: Int): Byte = unsafeArray(i) + override def hashCode = MurmurHash3.arrayHash(unsafeArray) + override def equals(that: Any) = that match { + case that: ofByte => Arrays.equals(unsafeArray, that.unsafeArray) + case _ => super.equals(that) + } + } + + final class ofShort(val unsafeArray: Array[Short]) extends ImmutableArray[Short] with Serializable { + def length: Int = unsafeArray.length + @throws[ArrayIndexOutOfBoundsException] + def apply(i: Int): Short = unsafeArray(i) + override def hashCode = MurmurHash3.arrayHash(unsafeArray) + override def equals(that: Any) = that match { + case that: ofShort => Arrays.equals(unsafeArray, that.unsafeArray) + case _ => super.equals(that) + } + } + + final class ofChar(val unsafeArray: Array[Char]) extends ImmutableArray[Char] with Serializable { + def length: Int = unsafeArray.length + @throws[ArrayIndexOutOfBoundsException] + def apply(i: Int): Char = unsafeArray(i) + override def hashCode = MurmurHash3.arrayHash(unsafeArray) + override def equals(that: Any) = that match { + case that: ofChar => Arrays.equals(unsafeArray, that.unsafeArray) + case _ => super.equals(that) + } + } + + final class ofInt(val unsafeArray: Array[Int]) extends ImmutableArray[Int] with Serializable { + def length: Int = unsafeArray.length + @throws[ArrayIndexOutOfBoundsException] + def apply(i: Int): Int = unsafeArray(i) + override def hashCode = MurmurHash3.arrayHash(unsafeArray) + override def equals(that: Any) = that match { + case that: ofInt => Arrays.equals(unsafeArray, that.unsafeArray) + case _ => super.equals(that) + } + } + + final class ofLong(val unsafeArray: Array[Long]) extends ImmutableArray[Long] with Serializable { + def length: Int = unsafeArray.length + @throws[ArrayIndexOutOfBoundsException] + def apply(i: Int): Long = unsafeArray(i) + override def hashCode = MurmurHash3.arrayHash(unsafeArray) + override def equals(that: Any) = that match { + case that: ofLong => Arrays.equals(unsafeArray, that.unsafeArray) + case _ => super.equals(that) + } + } + + final class ofFloat(val unsafeArray: Array[Float]) extends ImmutableArray[Float] with Serializable { + def length: Int = unsafeArray.length + @throws[ArrayIndexOutOfBoundsException] + def apply(i: Int): Float = unsafeArray(i) + override def hashCode = MurmurHash3.arrayHash(unsafeArray) + override def equals(that: Any) = that match { + case that: ofFloat => Arrays.equals(unsafeArray, that.unsafeArray) + case _ => super.equals(that) + } } + final class ofDouble(val unsafeArray: Array[Double]) extends ImmutableArray[Double] with Serializable { + def length: Int = unsafeArray.length + @throws[ArrayIndexOutOfBoundsException] + def apply(i: Int): Double = unsafeArray(i) + override def hashCode = MurmurHash3.arrayHash(unsafeArray) + override def equals(that: Any) = that match { + case that: ofDouble => Arrays.equals(unsafeArray, that.unsafeArray) + case _ => super.equals(that) + } + } + + final class ofBoolean(val unsafeArray: Array[Boolean]) extends ImmutableArray[Boolean] with Serializable { + def length: Int = unsafeArray.length + @throws[ArrayIndexOutOfBoundsException] + def apply(i: Int): Boolean = unsafeArray(i) + override def hashCode = MurmurHash3.arrayHash(unsafeArray) + override def equals(that: Any) = that match { + case that: ofBoolean => Arrays.equals(unsafeArray, that.unsafeArray) + case _ => super.equals(that) + } + } + + final class ofUnit(val unsafeArray: Array[Unit]) extends ImmutableArray[Unit] with Serializable { + def length: Int = unsafeArray.length + @throws[ArrayIndexOutOfBoundsException] + def apply(i: Int): Unit = unsafeArray(i) + override def hashCode = MurmurHash3.arrayHash(unsafeArray) + override def equals(that: Any) = that match { + case that: ofUnit => unsafeArray.length == that.unsafeArray.length + case _ => super.equals(that) + } + } } diff --git a/collections/src/main/scala/strawman/collection/mutable/ArrayBuffer.scala b/collections/src/main/scala/strawman/collection/mutable/ArrayBuffer.scala index b40b92f6e2..5238c207ee 100644 --- a/collections/src/main/scala/strawman/collection/mutable/ArrayBuffer.scala +++ b/collections/src/main/scala/strawman/collection/mutable/ArrayBuffer.scala @@ -31,10 +31,10 @@ import scala.Predef.intWrapper */ @SerialVersionUID(1529165946227428979L) class ArrayBuffer[A] private (initElems: Array[AnyRef], initLength: Int) - extends Buffer[A] + extends AbstractBuffer[A] with IndexedSeq[A] with IndexedSeqOps[A, ArrayBuffer, ArrayBuffer[A]] - with IndexedOptimizedSeq[A] + with IndexedOptimizedBuffer[A] with StrictOptimizedSeqOps[A, ArrayBuffer, ArrayBuffer[A]] with Serializable { diff --git a/collections/src/main/scala/strawman/collection/mutable/ArrayBuilder.scala b/collections/src/main/scala/strawman/collection/mutable/ArrayBuilder.scala new file mode 100644 index 0000000000..a979345664 --- /dev/null +++ b/collections/src/main/scala/strawman/collection/mutable/ArrayBuilder.scala @@ -0,0 +1,537 @@ +package strawman.collection +package mutable + +import scala.{Array, Serializable, Byte, Short, Char, Int, Long, Float, Double, Boolean, Unit, AnyRef, Any} +import scala.Predef.implicitly +import scala.reflect.ClassTag +import strawman.collection.immutable.ImmutableArray + +/** A builder class for arrays. + * + * @since 2.8 + * + * @tparam T the type of the elements for the builder. + */ +sealed abstract class ArrayBuilder[T] extends ReusableBuilder[T, Array[T]] with Serializable { + protected[this] var capacity: Int = 0 + protected var size: Int = 0 + + protected[this] final def ensureSize(size: Int): Unit = { + if (capacity < size || capacity == 0) { + var newsize = if (capacity == 0) 16 else capacity * 2 + while (newsize < size) newsize *= 2 + resize(newsize) + } + } + + override final def sizeHint(size: Int): Unit = + if (capacity < size) resize(size) + + final def clear(): Unit = size = 0 + + protected[this] def resize(size: Int): Unit +} + +/** A companion object for array builders. + * + * @since 2.8 + */ +object ArrayBuilder { + + /** Creates a new arraybuilder of type `T`. + * + * @tparam T type of the elements for the array builder, with a `ClassTag` context bound. + * @return a new empty array builder. + */ + def make[T: ClassTag](): ArrayBuilder[T] = { + val tag = implicitly[ClassTag[T]] + tag.runtimeClass match { + case java.lang.Byte.TYPE => new ArrayBuilder.ofByte().asInstanceOf[ArrayBuilder[T]] + case java.lang.Short.TYPE => new ArrayBuilder.ofShort().asInstanceOf[ArrayBuilder[T]] + case java.lang.Character.TYPE => new ArrayBuilder.ofChar().asInstanceOf[ArrayBuilder[T]] + case java.lang.Integer.TYPE => new ArrayBuilder.ofInt().asInstanceOf[ArrayBuilder[T]] + case java.lang.Long.TYPE => new ArrayBuilder.ofLong().asInstanceOf[ArrayBuilder[T]] + case java.lang.Float.TYPE => new ArrayBuilder.ofFloat().asInstanceOf[ArrayBuilder[T]] + case java.lang.Double.TYPE => new ArrayBuilder.ofDouble().asInstanceOf[ArrayBuilder[T]] + case java.lang.Boolean.TYPE => new ArrayBuilder.ofBoolean().asInstanceOf[ArrayBuilder[T]] + case java.lang.Void.TYPE => new ArrayBuilder.ofUnit().asInstanceOf[ArrayBuilder[T]] + case _ => new ArrayBuilder.ofRef[T with AnyRef]()(tag.asInstanceOf[ClassTag[T with AnyRef]]).asInstanceOf[ArrayBuilder[T]] + } + } + + /** A class for array builders for arrays of reference types. + * + * This builder can be reused. + * + * @tparam T type of elements for the array builder, subtype of `AnyRef` with a `ClassTag` context bound. + */ + final class ofRef[T <: AnyRef : ClassTag] extends ArrayBuilder[T] { + + private var elems: Array[T] = _ + + private def mkArray(size: Int): Array[T] = { + val newelems = new Array[T](size) + if (this.size > 0) Array.copy(elems, 0, newelems, 0, this.size) + newelems + } + + protected[this] def resize(size: Int): Unit = { + elems = mkArray(size) + capacity = size + } + + def add(elem: T): this.type = { + ensureSize(size + 1) + elems(size) = elem + size += 1 + this + } + + override def addAll(xs: IterableOnce[T]): this.type = (xs.asInstanceOf[AnyRef]) match { + case xs: ImmutableArray.ofRef[_] => + ensureSize(this.size + xs.length) + Array.copy(xs.unsafeArray, 0, elems, this.size, xs.length) + size += xs.length + this + case _ => + super.addAll(xs) + } + + def result() = { + if (capacity != 0 && capacity == size) { + capacity = 0 + elems + } + else mkArray(size) + } + + override def equals(other: Any): Boolean = other match { + case x: ofRef[_] => (size == x.size) && (elems == x.elems) + case _ => false + } + + override def toString = "ArrayBuilder.ofRef" + } + + /** A class for array builders for arrays of `byte`s. It can be reused. */ + final class ofByte extends ArrayBuilder[Byte] { + + private var elems: Array[Byte] = _ + + private def mkArray(size: Int): Array[Byte] = { + val newelems = new Array[Byte](size) + if (this.size > 0) Array.copy(elems, 0, newelems, 0, this.size) + newelems + } + + protected[this] def resize(size: Int): Unit = { + elems = mkArray(size) + capacity = size + } + + def add(elem: Byte): this.type = { + ensureSize(size + 1) + elems(size) = elem + size += 1 + this + } + + override def addAll(xs: IterableOnce[Byte]): this.type = xs match { + case xs: ImmutableArray.ofByte => + ensureSize(this.size + xs.length) + Array.copy(xs.unsafeArray, 0, elems, this.size, xs.length) + size += xs.length + this + case _ => + super.addAll(xs) + } + + def result() = { + if (capacity != 0 && capacity == size) { + capacity = 0 + elems + } + else mkArray(size) + } + + override def equals(other: Any): Boolean = other match { + case x: ofByte => (size == x.size) && (elems == x.elems) + case _ => false + } + + override def toString = "ArrayBuilder.ofByte" + } + + /** A class for array builders for arrays of `short`s. It can be reused. */ + final class ofShort extends ArrayBuilder[Short] { + + private var elems: Array[Short] = _ + + private def mkArray(size: Int): Array[Short] = { + val newelems = new Array[Short](size) + if (this.size > 0) Array.copy(elems, 0, newelems, 0, this.size) + newelems + } + + protected[this] def resize(size: Int): Unit = { + elems = mkArray(size) + capacity = size + } + + def add(elem: Short): this.type = { + ensureSize(size + 1) + elems(size) = elem + size += 1 + this + } + + override def addAll(xs: IterableOnce[Short]): this.type = xs match { + case xs: ImmutableArray.ofShort => + ensureSize(this.size + xs.length) + Array.copy(xs.unsafeArray, 0, elems, this.size, xs.length) + size += xs.length + this + case _ => + super.addAll(xs) + } + + def result() = { + if (capacity != 0 && capacity == size) { + capacity = 0 + elems + } + else mkArray(size) + } + + override def equals(other: Any): Boolean = other match { + case x: ofShort => (size == x.size) && (elems == x.elems) + case _ => false + } + + override def toString = "ArrayBuilder.ofShort" + } + + /** A class for array builders for arrays of `char`s. It can be reused. */ + final class ofChar extends ArrayBuilder[Char] { + + private var elems: Array[Char] = _ + + private def mkArray(size: Int): Array[Char] = { + val newelems = new Array[Char](size) + if (this.size > 0) Array.copy(elems, 0, newelems, 0, this.size) + newelems + } + + protected[this] def resize(size: Int): Unit = { + elems = mkArray(size) + capacity = size + } + + def add(elem: Char): this.type = { + ensureSize(size + 1) + elems(size) = elem + size += 1 + this + } + + override def addAll(xs: IterableOnce[Char]): this.type = xs match { + case xs: ImmutableArray.ofChar => + ensureSize(this.size + xs.length) + Array.copy(xs.unsafeArray, 0, elems, this.size, xs.length) + size += xs.length + this + case _ => + super.addAll(xs) + } + + def result() = { + if (capacity != 0 && capacity == size) { + capacity = 0 + elems + } + else mkArray(size) + } + + override def equals(other: Any): Boolean = other match { + case x: ofChar => (size == x.size) && (elems == x.elems) + case _ => false + } + + override def toString = "ArrayBuilder.ofChar" + } + + /** A class for array builders for arrays of `int`s. It can be reused. */ + final class ofInt extends ArrayBuilder[Int] { + + private var elems: Array[Int] = _ + + private def mkArray(size: Int): Array[Int] = { + val newelems = new Array[Int](size) + if (this.size > 0) Array.copy(elems, 0, newelems, 0, this.size) + newelems + } + + protected[this] def resize(size: Int): Unit = { + elems = mkArray(size) + capacity = size + } + + def add(elem: Int): this.type = { + ensureSize(size + 1) + elems(size) = elem + size += 1 + this + } + + override def addAll(xs: IterableOnce[Int]): this.type = xs match { + case xs: ImmutableArray.ofInt => + ensureSize(this.size + xs.length) + Array.copy(xs.unsafeArray, 0, elems, this.size, xs.length) + size += xs.length + this + case _ => + super.addAll(xs) + } + + def result() = { + if (capacity != 0 && capacity == size) { + capacity = 0 + elems + } + else mkArray(size) + } + + override def equals(other: Any): Boolean = other match { + case x: ofInt => (size == x.size) && (elems == x.elems) + case _ => false + } + + override def toString = "ArrayBuilder.ofInt" + } + + /** A class for array builders for arrays of `long`s. It can be reused. */ + final class ofLong extends ArrayBuilder[Long] { + + private var elems: Array[Long] = _ + + private def mkArray(size: Int): Array[Long] = { + val newelems = new Array[Long](size) + if (this.size > 0) Array.copy(elems, 0, newelems, 0, this.size) + newelems + } + + protected[this] def resize(size: Int): Unit = { + elems = mkArray(size) + capacity = size + } + + def add(elem: Long): this.type = { + ensureSize(size + 1) + elems(size) = elem + size += 1 + this + } + + override def addAll(xs: IterableOnce[Long]): this.type = xs match { + case xs: ImmutableArray.ofLong => + ensureSize(this.size + xs.length) + Array.copy(xs.unsafeArray, 0, elems, this.size, xs.length) + size += xs.length + this + case _ => + super.addAll(xs) + } + + def result() = { + if (capacity != 0 && capacity == size) { + capacity = 0 + elems + } + else mkArray(size) + } + + override def equals(other: Any): Boolean = other match { + case x: ofLong => (size == x.size) && (elems == x.elems) + case _ => false + } + + override def toString = "ArrayBuilder.ofLong" + } + + /** A class for array builders for arrays of `float`s. It can be reused. */ + final class ofFloat extends ArrayBuilder[Float] { + + private var elems: Array[Float] = _ + + private def mkArray(size: Int): Array[Float] = { + val newelems = new Array[Float](size) + if (this.size > 0) Array.copy(elems, 0, newelems, 0, this.size) + newelems + } + + protected[this] def resize(size: Int): Unit = { + elems = mkArray(size) + capacity = size + } + + def add(elem: Float): this.type = { + ensureSize(size + 1) + elems(size) = elem + size += 1 + this + } + + override def addAll(xs: IterableOnce[Float]): this.type = xs match { + case xs: ImmutableArray.ofFloat => + ensureSize(this.size + xs.length) + Array.copy(xs.unsafeArray, 0, elems, this.size, xs.length) + size += xs.length + this + case _ => + super.addAll(xs) + } + + def result() = { + if (capacity != 0 && capacity == size) { + capacity = 0 + elems + } + else mkArray(size) + } + + override def equals(other: Any): Boolean = other match { + case x: ofFloat => (size == x.size) && (elems == x.elems) + case _ => false + } + + override def toString = "ArrayBuilder.ofFloat" + } + + /** A class for array builders for arrays of `double`s. It can be reused. */ + final class ofDouble extends ArrayBuilder[Double] { + + private var elems: Array[Double] = _ + + private def mkArray(size: Int): Array[Double] = { + val newelems = new Array[Double](size) + if (this.size > 0) Array.copy(elems, 0, newelems, 0, this.size) + newelems + } + + protected[this] def resize(size: Int): Unit = { + elems = mkArray(size) + capacity = size + } + + def add(elem: Double): this.type = { + ensureSize(size + 1) + elems(size) = elem + size += 1 + this + } + + override def addAll(xs: IterableOnce[Double]): this.type = xs match { + case xs: ImmutableArray.ofDouble => + ensureSize(this.size + xs.length) + Array.copy(xs.unsafeArray, 0, elems, this.size, xs.length) + size += xs.length + this + case _ => + super.addAll(xs) + } + + def result() = { + if (capacity != 0 && capacity == size) { + capacity = 0 + elems + } + else mkArray(size) + } + + override def equals(other: Any): Boolean = other match { + case x: ofDouble => (size == x.size) && (elems == x.elems) + case _ => false + } + + override def toString = "ArrayBuilder.ofDouble" + } + + /** A class for array builders for arrays of `boolean`s. It can be reused. */ + class ofBoolean extends ArrayBuilder[Boolean] { + + private var elems: Array[Boolean] = _ + + private def mkArray(size: Int): Array[Boolean] = { + val newelems = new Array[Boolean](size) + if (this.size > 0) Array.copy(elems, 0, newelems, 0, this.size) + newelems + } + + protected[this] def resize(size: Int): Unit = { + elems = mkArray(size) + capacity = size + } + + def add(elem: Boolean): this.type = { + ensureSize(size + 1) + elems(size) = elem + size += 1 + this + } + + override def addAll(xs: IterableOnce[Boolean]): this.type = xs match { + case xs: ImmutableArray.ofBoolean => + ensureSize(this.size + xs.length) + Array.copy(xs.unsafeArray, 0, elems, this.size, xs.length) + size += xs.length + this + case _ => + super.addAll(xs) + } + + def result() = { + if (capacity != 0 && capacity == size) { + capacity = 0 + elems + } + else mkArray(size) + } + + override def equals(other: Any): Boolean = other match { + case x: ofBoolean => (size == x.size) && (elems == x.elems) + case _ => false + } + + override def toString = "ArrayBuilder.ofBoolean" + } + + /** A class for array builders for arrays of `Unit` type. It can be reused. */ + final class ofUnit extends ArrayBuilder[Unit] { + + def add(elem: Unit): this.type = { + size += 1 + this + } + + override def addAll(xs: IterableOnce[Unit]): this.type = { + size += xs.iterator().size + this + } + + def result() = { + val ans = new Array[Unit](size) + var i = 0 + while (i < size) { ans(i) = (); i += 1 } + ans + } + + override def equals(other: Any): Boolean = other match { + case x: ofUnit => (size == x.size) + case _ => false + } + + protected[this] def resize(size: Int): Unit = () + + override def toString = "ArrayBuilder.ofUnit" + } +} diff --git a/collections/src/main/scala/strawman/collection/mutable/Buffer.scala b/collections/src/main/scala/strawman/collection/mutable/Buffer.scala index bcb1038b6a..525585f83a 100644 --- a/collections/src/main/scala/strawman/collection/mutable/Buffer.scala +++ b/collections/src/main/scala/strawman/collection/mutable/Buffer.scala @@ -1,9 +1,14 @@ package strawman.collection package mutable -import scala.`inline` +import scala.{Int, `inline`, throws, IndexOutOfBoundsException, IllegalArgumentException, Unit, Boolean, Array} +import scala.Predef.intWrapper + +/** A `Buffer` is a growable and shrinkable `Seq`. */ +trait Buffer[A] extends Seq[A] + with Growable[A] + with Shrinkable[A] { -trait Buffer[A] extends Seq[A] with Shrinkable[A] { //TODO Prepend is a logical choice for a readable name of `+=:` but it conflicts with the renaming of `append` to `add` /** Prepends a single element at the front of this $coll. * @@ -14,6 +19,106 @@ trait Buffer[A] extends Seq[A] with Shrinkable[A] { /** Alias for `prepend` */ @`inline` final def +=: (elem: A): this.type = prepend(elem) + + @throws[IndexOutOfBoundsException] + def insert(idx: Int, elem: A): Unit + + /** Inserts new elements at the index `idx`. Opposed to method + * `update`, this method will not replace an element with a new + * one. Instead, it will insert a new element at index `idx`. + * + * @param idx the index where a new element will be inserted. + * @param elems the iterable object providing all elements to insert. + * @throws IndexOutOfBoundsException if `idx` is out of bounds. + */ + @throws[IndexOutOfBoundsException] + def insertAll(idx: Int, elems: IterableOnce[A]): Unit + + /** Removes the element at a given index position. + * + * @param idx the index which refers to the element to delete. + * @return the element that was formerly at index `idx`. + */ + @throws[IndexOutOfBoundsException] + def remove(idx: Int): A + + /** Removes the element on a given index position. It takes time linear in + * the buffer size. + * + * @param idx the index which refers to the first element to remove. + * @param count the number of elements to remove. + * @throws IndexOutOfBoundsException if the index `idx` is not in the valid range + * `0 <= idx <= length - count` (with `count > 0`). + * @throws IllegalArgumentException if `count < 0`. + */ + @throws[IndexOutOfBoundsException] + @throws[IllegalArgumentException] + def remove(idx: Int, count: Int): Unit + + def patchInPlace(from: Int, patch: strawman.collection.Seq[A], replaced: Int): this.type + + // +=, ++=, clear inherited from Growable + // Per remark of @ichoran, we should preferably not have these: + // + // 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 IndexedOptimizedBuffer[A] extends IndexedOptimizedSeq[A] with Buffer[A] { + + def flatMapInPlace(f: A => IterableOnce[A]): this.type = { + // There's scope for a better implementation which copies elements in place. + 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 + while (i < size && p(apply(i))) i += 1 + var j = 1 + while (i < size) { + if (p(apply(i))) { + this(j) = this(i) + j += 1 + } + i += 1 + } + takeInPlace(j) + } + + def patchInPlace(from: Int, patch: strawman.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 + } } /** Explicit instantiation of the `Buffer` trait to reduce class file size in subclasses. */ diff --git a/collections/src/main/scala/strawman/collection/mutable/IndexedSeq.scala b/collections/src/main/scala/strawman/collection/mutable/IndexedSeq.scala new file mode 100644 index 0000000000..3fe8b66168 --- /dev/null +++ b/collections/src/main/scala/strawman/collection/mutable/IndexedSeq.scala @@ -0,0 +1,10 @@ +package strawman.collection +package mutable + +trait IndexedSeq[T] extends Seq[T] + with strawman.collection.IndexedSeq[T] + with IndexedSeqOps[T, IndexedSeq, IndexedSeq[T]] + +trait IndexedSeqOps[A, +CC[X] <: IndexedSeq[X], +C <: IndexedSeq[A]] + extends strawman.collection.IndexedSeqOps[A, CC, C] + with SeqOps[A, CC, C] diff --git a/collections/src/main/scala/strawman/collection/mutable/Iterable.scala b/collections/src/main/scala/strawman/collection/mutable/Iterable.scala index e3b35b8f4a..5fdf5324f1 100644 --- a/collections/src/main/scala/strawman/collection/mutable/Iterable.scala +++ b/collections/src/main/scala/strawman/collection/mutable/Iterable.scala @@ -14,15 +14,9 @@ trait Iterable[A] * @define Coll `mutable.Iterable` */ trait IterableOps[A, +CC[X], +C] - extends collection.IterableOps[A, CC, C] - with Growable[A] { + extends collection.IterableOps[A, CC, C] { def mapInPlace(f: A => A): this.type - - def flatMapInPlace(f: A => IterableOnce[A]): this.type - - def filterInPlace(p: A => Boolean): this.type - } /** diff --git a/collections/src/main/scala/strawman/collection/mutable/Map.scala b/collections/src/main/scala/strawman/collection/mutable/Map.scala index 51853cd3fb..5db8ef7b3f 100644 --- a/collections/src/main/scala/strawman/collection/mutable/Map.scala +++ b/collections/src/main/scala/strawman/collection/mutable/Map.scala @@ -53,6 +53,7 @@ trait Map[K, V] trait MapOps[K, V, +CC[X, Y] <: MapOps[X, Y, CC, _], +C <: MapOps[K, V, CC, C]] extends IterableOps[(K, V), Iterable, C] with collection.MapOps[K, V, CC, C] + with Growable[(K, V)] with Shrinkable[K] { def iterableFactory = Iterable diff --git a/collections/src/main/scala/strawman/collection/mutable/Seq.scala b/collections/src/main/scala/strawman/collection/mutable/Seq.scala index 104fbb9345..76efdb5a2d 100644 --- a/collections/src/main/scala/strawman/collection/mutable/Seq.scala +++ b/collections/src/main/scala/strawman/collection/mutable/Seq.scala @@ -24,8 +24,7 @@ object Seq extends SeqFactory.Delegate[Seq](ArrayBuffer) */ trait SeqOps[A, +CC[X] <: Seq[X], +C <: Seq[A]] extends IterableOps[A, CC, C] - with collection.SeqOps[A, CC, C] - with Shrinkable[A] { + with collection.SeqOps[A, CC, C] { override def clone(): C = { val b = newSpecificBuilder() @@ -41,69 +40,6 @@ trait SeqOps[A, +CC[X] <: Seq[X], +C <: Seq[A]] */ @throws[IndexOutOfBoundsException] def update(idx: Int, elem: A): Unit - - @throws[IndexOutOfBoundsException] - def insert(idx: Int, elem: A): Unit - - /** Inserts new elements at the index `idx`. Opposed to method - * `update`, this method will not replace an element with a new - * one. Instead, it will insert a new element at index `idx`. - * - * @param idx the index where a new element will be inserted. - * @param elems the iterable object providing all elements to insert. - * @throws IndexOutOfBoundsException if `idx` is out of bounds. - */ - @throws[IndexOutOfBoundsException] - def insertAll(idx: Int, elems: IterableOnce[A]): Unit - - /** Removes the element at a given index position. - * - * @param idx the index which refers to the element to delete. - * @return the element that was formerly at index `idx`. - */ - @throws[IndexOutOfBoundsException] - def remove(idx: Int): A - - /** Removes the element on a given index position. It takes time linear in - * the buffer size. - * - * @param idx the index which refers to the first element to remove. - * @param count the number of elements to remove. - * @throws IndexOutOfBoundsException if the index `idx` is not in the valid range - * `0 <= idx <= length - count` (with `count > 0`). - * @throws IllegalArgumentException if `count < 0`. - */ - @throws[IndexOutOfBoundsException] - @throws[IllegalArgumentException] - def remove(idx: Int, count: Int): Unit - - def patchInPlace(from: Int, patch: collection.Seq[A], replaced: Int): this.type - - // +=, ++=, clear inherited from Growable - // Per remark of @ichoran, we should preferably not have these: - // - // 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] { @@ -114,40 +50,6 @@ trait IndexedOptimizedSeq[A] extends Seq[A] { while (i < siz) { this(i) = f(this(i)); i += 1 } this } - - def flatMapInPlace(f: A => IterableOnce[A]): this.type = { - // There's scope for a better implementation which copies elements in place. - 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 - while (i < size && p(apply(i))) i += 1 - var j = 1 - 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 - } } /** Explicit instantiation of the `Seq` trait to reduce class file size in subclasses. */ diff --git a/collections/src/main/scala/strawman/collection/mutable/Set.scala b/collections/src/main/scala/strawman/collection/mutable/Set.scala index 9f0983ecfe..738ce2c2cf 100644 --- a/collections/src/main/scala/strawman/collection/mutable/Set.scala +++ b/collections/src/main/scala/strawman/collection/mutable/Set.scala @@ -18,6 +18,7 @@ trait Set[A] trait SetOps[A, +CC[X], +C <: SetOps[A, CC, C]] extends IterableOps[A, CC, C] with collection.SetOps[A, CC, C] + with Growable[A] with Shrinkable[A] { def mapInPlace(f: A => A): this.type = { diff --git a/collections/src/main/scala/strawman/collection/mutable/WrappedArray.scala b/collections/src/main/scala/strawman/collection/mutable/WrappedArray.scala new file mode 100644 index 0000000000..ba7b95d60f --- /dev/null +++ b/collections/src/main/scala/strawman/collection/mutable/WrappedArray.scala @@ -0,0 +1,242 @@ +package strawman.collection +package mutable + +import scala.{Unit, Int, Array, Boolean, Any, Byte, Short, Char, Long, Float, Double, AnyRef, Serializable, specialized, `inline`} +import scala.Predef.{Class, implicitly} +import scala.runtime.ScalaRunTime +import scala.reflect.ClassTag +import scala.util.hashing.MurmurHash3 + +import java.util.Arrays + +/** + * A collection representing `Array[T]`. Unlike `ArrayBuffer` it is always backed by the same + * underlying `Array`, therefore it is not growable or shrinkable. + * + * @tparam T type of the elements in this wrapped array. + * + * @author Martin Odersky, Stephane Micheloud + * @version 1.0 + * @since 2.8 + * @define Coll `WrappedArray` + * @define coll wrapped array + * @define orderDependent + * @define orderDependentFold + * @define mayNotTerminateInf + * @define willNotTerminateInf + */ +abstract class WrappedArray[T] + extends AbstractSeq[T] + with IndexedSeq[T] + with IndexedSeqOps[T, WrappedArray, WrappedArray[T]] + with ArrayLike[T] + with IndexedOptimizedSeq[T] + with StrictOptimizedSeqOps[T, WrappedArray, WrappedArray[T]] + with Serializable { + + def iterableFactory: strawman.collection.SeqFactory[WrappedArray] = WrappedArray + + protected[this] def fromSpecificIterable(coll: strawman.collection.Iterable[T]): WrappedArray[T] = { + val b = ArrayBuilder.make()(elemTag) + val s = coll.knownSize + if(s > 0) b.sizeHint(s) + b ++= coll + WrappedArray.make(b.result()) + } + protected[this] def newSpecificBuilder(): Builder[T, WrappedArray[T]] = WrappedArray.newBuilder(elemTag) + + /** The tag of the element type */ + def elemTag: ClassTag[T] + + /** Update element at given index */ + def update(index: Int, elem: T): Unit + + /** The underlying array */ + def array: Array[T] + + override def toArray[U >: T : ClassTag]: Array[U] = { + val thatElementClass = implicitly[ClassTag[U]].runtimeClass + if (array.getClass.getComponentType eq thatElementClass) + array.asInstanceOf[Array[U]] + else + super.toArray[U] + } + + override def className = "WrappedArray" + + /** Clones this object, including the underlying Array. */ + override def clone(): WrappedArray[T] = WrappedArray.make(array.clone()) +} + +/** A companion object used to create instances of `WrappedArray`. + */ +object WrappedArray extends StrictOptimizedSeqFactory[WrappedArray] { + // This is reused for all calls to empty. + private val EmptyWrappedArray = new ofRef[AnyRef](new Array[AnyRef](0)) + def empty[T]: WrappedArray[T] = EmptyWrappedArray.asInstanceOf[WrappedArray[T]] + + def from[A](it: strawman.collection.IterableOnce[A]): WrappedArray[A] = { + val n = it.knownSize + if (n > -1) { + val elements = scala.Array.ofDim[Any](n) + val iterator = it.iterator() + var i = 0 + while (i < n) { + ScalaRunTime.array_update(elements, i, iterator.next()) + i = i + 1 + } + make(elements) + } else fromArrayBuffer(ArrayBuffer.from(it)) + } + + @`inline` private def fromArrayBuffer[A](arr: ArrayBuffer[A]): WrappedArray[A] = + make[A](arr.asInstanceOf[ArrayBuffer[Any]].toArray) + + def newBuilder[A](): Builder[A, WrappedArray[A]] = + ArrayBuffer.newBuilder[A]().mapResult(fromArrayBuffer) + + def newBuilder[A](implicit t: ClassTag[A]): Builder[A, WrappedArray[A]] = ArrayBuilder.make[A]()(t).mapResult(make) + + // If make is called explicitly we use whatever we're given, even if it's + // empty. This may be unnecessary (if WrappedArray is to honor the collections + // contract all empty ones must be equal, so discriminating based on the reference + // equality of an empty array should not come up) but we may as well be + // conservative since wrapRefArray contributes most of the unnecessary allocations. + def make[T](x: Array[_]): WrappedArray[T] = (x match { + case null => null + case x: Array[AnyRef] => new ofRef[AnyRef](x) + case x: Array[Int] => new ofInt(x) + case x: Array[Double] => new ofDouble(x) + case x: Array[Long] => new ofLong(x) + case x: Array[Float] => new ofFloat(x) + case x: Array[Char] => new ofChar(x) + case x: Array[Byte] => new ofByte(x) + case x: Array[Short] => new ofShort(x) + case x: Array[Boolean] => new ofBoolean(x) + case x: Array[Unit] => new ofUnit(x) + }).asInstanceOf[WrappedArray[T]] + + final class ofRef[T <: AnyRef](val array: Array[T]) extends WrappedArray[T] with Serializable { + lazy val elemTag = ClassTag[T](array.getClass.getComponentType) + def length: Int = array.length + def apply(index: Int): T = array(index).asInstanceOf[T] + def update(index: Int, elem: T): Unit = { array(index) = elem } + override def hashCode = wrappedArrayHash(array) + override def equals(that: Any) = that match { + case that: ofRef[_] => Arrays.equals(array.asInstanceOf[Array[AnyRef]], that.array.asInstanceOf[Array[AnyRef]]) + case _ => super.equals(that) + } + } + + final class ofByte(val array: Array[Byte]) extends WrappedArray[Byte] with Serializable { + def elemTag = ClassTag.Byte + def length: Int = array.length + def apply(index: Int): Byte = array(index) + def update(index: Int, elem: Byte): Unit = { array(index) = elem } + override def hashCode = wrappedBytesHash(array) + override def equals(that: Any) = that match { + case that: ofByte => Arrays.equals(array, that.array) + case _ => super.equals(that) + } + } + + final class ofShort(val array: Array[Short]) extends WrappedArray[Short] with Serializable { + def elemTag = ClassTag.Short + def length: Int = array.length + def apply(index: Int): Short = array(index) + def update(index: Int, elem: Short): Unit = { array(index) = elem } + override def hashCode = wrappedArrayHash(array) + override def equals(that: Any) = that match { + case that: ofShort => Arrays.equals(array, that.array) + case _ => super.equals(that) + } + } + + final class ofChar(val array: Array[Char]) extends WrappedArray[Char] with Serializable { + def elemTag = ClassTag.Char + def length: Int = array.length + def apply(index: Int): Char = array(index) + def update(index: Int, elem: Char): Unit = { array(index) = elem } + override def hashCode = wrappedArrayHash(array) + override def equals(that: Any) = that match { + case that: ofChar => Arrays.equals(array, that.array) + case _ => super.equals(that) + } + } + + final class ofInt(val array: Array[Int]) extends WrappedArray[Int] with Serializable { + def elemTag = ClassTag.Int + def length: Int = array.length + def apply(index: Int): Int = array(index) + def update(index: Int, elem: Int): Unit = { array(index) = elem } + override def hashCode = wrappedArrayHash(array) + override def equals(that: Any) = that match { + case that: ofInt => Arrays.equals(array, that.array) + case _ => super.equals(that) + } + } + + final class ofLong(val array: Array[Long]) extends WrappedArray[Long] with Serializable { + def elemTag = ClassTag.Long + def length: Int = array.length + def apply(index: Int): Long = array(index) + def update(index: Int, elem: Long): Unit = { array(index) = elem } + override def hashCode = wrappedArrayHash(array) + override def equals(that: Any) = that match { + case that: ofLong => Arrays.equals(array, that.array) + case _ => super.equals(that) + } + } + + final class ofFloat(val array: Array[Float]) extends WrappedArray[Float] with Serializable { + def elemTag = ClassTag.Float + def length: Int = array.length + def apply(index: Int): Float = array(index) + def update(index: Int, elem: Float): Unit = { array(index) = elem } + override def hashCode = wrappedArrayHash(array) + override def equals(that: Any) = that match { + case that: ofFloat => Arrays.equals(array, that.array) + case _ => super.equals(that) + } + } + + final class ofDouble(val array: Array[Double]) extends WrappedArray[Double] with Serializable { + def elemTag = ClassTag.Double + def length: Int = array.length + def apply(index: Int): Double = array(index) + def update(index: Int, elem: Double): Unit = { array(index) = elem } + override def hashCode = wrappedArrayHash(array) + override def equals(that: Any) = that match { + case that: ofDouble => Arrays.equals(array, that.array) + case _ => super.equals(that) + } + } + + final class ofBoolean(val array: Array[Boolean]) extends WrappedArray[Boolean] with Serializable { + def elemTag = ClassTag.Boolean + def length: Int = array.length + def apply(index: Int): Boolean = array(index) + def update(index: Int, elem: Boolean): Unit = { array(index) = elem } + override def hashCode = wrappedArrayHash(array) + override def equals(that: Any) = that match { + case that: ofBoolean => Arrays.equals(array, that.array) + case _ => super.equals(that) + } + } + + final class ofUnit(val array: Array[Unit]) extends WrappedArray[Unit] with Serializable { + def elemTag = ClassTag.Unit + def length: Int = array.length + def apply(index: Int): Unit = array(index) + def update(index: Int, elem: Unit): Unit = { array(index) = elem } + override def hashCode = wrappedArrayHash(array) + override def equals(that: Any) = that match { + case that: ofUnit => array.length == that.array.length + case _ => super.equals(that) + } + } + + //TODO Use MurmurHash3.wrappedArrayHash/wrappedBytesHash which are private[scala] + private def wrappedArrayHash[@specialized T](a: Array[T]): Int = MurmurHash3.arrayHash(a, MurmurHash3.seqSeed) + private def wrappedBytesHash(data: Array[Byte]): Int = MurmurHash3.bytesHash(data, MurmurHash3.seqSeed) +} diff --git a/collections/src/main/scala/strawman/collection/package.scala b/collections/src/main/scala/strawman/collection/package.scala index d19087c09b..df22e37da7 100644 --- a/collections/src/main/scala/strawman/collection/package.scala +++ b/collections/src/main/scala/strawman/collection/package.scala @@ -103,8 +103,8 @@ 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] = ArrayView[T](xs) + /** Convert array to WrappedArray. Lower priority than ArrayOps */ + implicit def arrayToWrappedArray[T](xs: Array[T]): mutable.IndexedSeq[T] = mutable.WrappedArray.make[T](xs) /** Convert string to iterable via view. Lower priority than StringOps */ implicit def stringToView(s: String): immutable.StringView = new immutable.StringView(s)