Skip to content
This repository was archived by the owner on Dec 22, 2021. It is now read-only.

Add groupBy #108

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ See the [CONTRIBUTING](CONTRIBUTING.md) file.
- [x] `foldLeft`
- [x] `foldRight`
- [x] `get`
- [x] `getOrElse`
- [x] `getOrElseUpdate`
- [x] `head`
- [x] `indexWhere`
- [x] `isDefinedAt`
Expand All @@ -88,7 +90,7 @@ See the [CONTRIBUTING](CONTRIBUTING.md) file.
- [x] `drop`
- [x] `empty`
- [x] `filter` / `filterNot`
- [ ] `groupBy`
- [x] `groupBy`
- [x] `intersect`
- [x] `partition`
- [x] `range`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,10 @@ class HashSetBenchmark {
@Benchmark
def map(bh: Blackhole): Unit = bh.consume(xs.map(x => x + 1))

@Benchmark
def groupBy(bh: Blackhole): Unit = {
val result = xs.groupBy(_ % 5)
bh.consume(result)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -87,4 +87,10 @@ class ImmutableArrayBenchmark {
@Benchmark
def map(bh: Blackhole): Unit = bh.consume(xs.map(x => x + 1))

@Benchmark
def groupBy(bh: Blackhole): Unit = {
val result = xs.groupBy(_ % 5)
bh.consume(result)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,10 @@ class LazyListBenchmark {
@Benchmark
def map(bh: Blackhole): Unit = bh.consume(xs.map(x => x + 1))

@Benchmark
def groupBy(bh: Blackhole): Unit = {
val result = xs.groupBy(_ % 5)
bh.consume(result)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -87,4 +87,10 @@ class ListBenchmark {
@Benchmark
def map(bh: Blackhole): Unit = bh.consume(xs.map(x => x + 1))

@Benchmark
def groupBy(bh: Blackhole): Unit = {
val result = xs.groupBy(_ % 5)
bh.consume(result)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ package strawman.collection.immutable
import java.util.concurrent.TimeUnit

import org.openjdk.jmh.annotations._
import scala.{Any, AnyRef, Int, Unit}
import org.openjdk.jmh.infra.Blackhole

import scala.{Any, AnyRef, Int, Long, Unit}
import scala.Predef.intWrapper

@BenchmarkMode(scala.Array(Mode.AverageTime))
@OutputTimeUnit(TimeUnit.NANOSECONDS)
Expand All @@ -13,46 +16,81 @@ import scala.{Any, AnyRef, Int, Unit}
@State(Scope.Benchmark)
class PrimitiveArrayBenchmark {

@Param(scala.Array("8", "64", "512", "4096", "32768", "262144"/*, "2097152"*/))
@Param(scala.Array("0", "1", "2", "3", "4", "7", "8", "15", "16", "17", "39", "282", "73121", "7312102"))
var size: Int = _

var xs: ImmutableArray[Int] = _
var obj: Int = _
var xss: scala.Array[ImmutableArray[Int]] = _
var randomIndices: scala.Array[Int] = _

@Setup(Level.Trial)
def initData(): Unit = {
xs = ImmutableArray.fill(size)(obj)
obj = 123
def freshCollection() = ImmutableArray((1 to size): _*)
xs = freshCollection()
xss = scala.Array.fill(1000)(freshCollection())
if (size > 0) {
randomIndices = scala.Array.fill(1000)(scala.util.Random.nextInt(size))
}
}

@Benchmark
def cons(): Any = {
// @OperationsPerInvocation(size)
def cons(bh: Blackhole): Unit = {
var ys = ImmutableArray.empty[Int]
var i = 0
while (i < size) {
ys = ys :+ obj
i += 1
ys = ys :+ i
i = i + 1
}
ys
bh.consume(ys)
}

@Benchmark
def uncons(): Any = xs.tail
def uncons(bh: Blackhole): Unit = bh.consume(xs.tail)

@Benchmark
def concat(bh: Blackhole): Unit = bh.consume(xs ++ xs)

@Benchmark
def foreach(bh: Blackhole): Unit = xs.foreach(x => bh.consume(x))

@Benchmark
// @OperationsPerInvocation(size)
def foreach_while(bh: Blackhole): Unit = {
var ys = xs
while (ys.nonEmpty) {
bh.consume(ys.head)
ys = ys.tail
}
}

@Benchmark
def concat(): Any = xs ++ xs
@OperationsPerInvocation(1000)
def lookupLast(bh: Blackhole): Unit = {
var i = 0
while (i < 1000) {
bh.consume(xss(i)(size - 1))
i = i + 1
}
}

@Benchmark
def foreach(): Any = {
var n = 0
xs.foreach(x => if (x == 0) n += 1)
n
@OperationsPerInvocation(1000)
def randomLookup(bh: Blackhole): Unit = {
var i = 0
while (i < 1000) {
bh.consume(xs(randomIndices(i)))
i = i + 1
}
}

@Benchmark
def lookup(): Any = xs(size - 1)
def map(bh: Blackhole): Unit = bh.consume(xs.map(x => x + 1))

@Benchmark
def map(): Any = xs.map(x => if (x == 0) "foo" else "bar")
def groupBy(bh: Blackhole): Unit = {
val result = xs.groupBy(_ % 5)
bh.consume(result)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,10 @@ class ScalaHashSetBenchmark {
@Benchmark
def map(bh: Blackhole): Unit = bh.consume(xs.map(x => x + 1))

@Benchmark
def groupBy(bh: Blackhole): Unit = {
val result = xs.groupBy(_ % 5)
bh.consume(result)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -87,4 +87,10 @@ class ScalaListBenchmark {
@Benchmark
def map(bh: Blackhole): Unit = bh.consume(xs.map(x => x + 1))

@Benchmark
def groupBy(bh: Blackhole): Unit = {
val result = xs.groupBy(_ % 5)
bh.consume(result)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,10 @@ class ScalaTreeSetBenchmark {
@Benchmark
def map(bh: Blackhole): Unit = bh.consume(xs.map(x => x + 1))

@Benchmark
def groupBy(bh: Blackhole): Unit = {
val result = xs.groupBy(_ % 5)
bh.consume(result)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ package strawman.collection.immutable
import java.util.concurrent.TimeUnit

import org.openjdk.jmh.annotations._
import scala.{Any, AnyRef, Int, Unit}
import org.openjdk.jmh.infra.Blackhole

import scala.{Any, AnyRef, Int, Long, Unit}
import scala.Predef.intWrapper

@BenchmarkMode(scala.Array(Mode.AverageTime))
@OutputTimeUnit(TimeUnit.NANOSECONDS)
Expand All @@ -16,43 +19,67 @@ class ScalaVectorBenchmark {
@Param(scala.Array("8", "64", "512", "4096", "32768", "262144"/*, "2097152"*/))
var size: Int = _

var xs: scala.Vector[AnyRef] = _
var obj: Any = _
var xs: scala.Vector[Long] = _
var xss: scala.Array[scala.Vector[Long]] = _
var randomIndices: scala.Array[Int] = _

@Setup(Level.Trial)
def initData(): Unit = {
xs = scala.Vector.fill(size)("")
obj = ""
def freshCollection() = scala.Vector((1 to size).map(_.toLong): _*)
xs = freshCollection()
xss = scala.Array.fill(1000)(freshCollection())
if (size > 0) {
randomIndices = scala.Array.fill(1000)(scala.util.Random.nextInt(size))
}
}

@Benchmark
def cons(): Any = {
var ys = scala.Vector.empty[Any]
var i = 0
def cons(bh: Blackhole): Unit = {
var ys = scala.Vector.empty[Long]
var i = 0L
while (i < size) {
ys = ys :+ obj
ys = ys :+ i
i += 1
}
ys
bh.consume(ys)
}

@Benchmark
def uncons(): Any = xs.tail
def uncons(bh: Blackhole): Unit = bh.consume(xs.tail)

@Benchmark
def concat(): Any = xs ++ xs
def concat(bh: Blackhole): Unit = bh.consume(xs ++ xs)

@Benchmark
def foreach(): Any = {
var n = 0
xs.foreach(x => if (x eq null) n += 1)
n
def foreach(bh: Blackhole): Unit = xs.foreach(x => bh.consume(x))

@Benchmark
@OperationsPerInvocation(1000)
def lookupLast(bh: Blackhole): Unit = {
var i = 0
while (i < 1000) {
bh.consume(xss(i)(size - 1))
i = i + 1
}
}

@Benchmark
def lookup(): Any = xs(size - 1)
@OperationsPerInvocation(1000)
def randomLookup(bh: Blackhole): Unit = {
var i = 0
while (i < 1000) {
bh.consume(xs(randomIndices(i)))
i = i + 1
}
}

@Benchmark
def map(): Any = xs.map(x => if (x eq null) "foo" else "bar")
def map(bh: Blackhole): Unit = bh.consume(xs.map(x => x + 1))

@Benchmark
def groupBy(bh: Blackhole): Unit = {
val result = xs.groupBy(_ % 5)
bh.consume(result)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,10 @@ class TreeSetBenchmark {
@Benchmark
def map(bh: Blackhole): Unit = bh.consume(xs.map(x => x + 1))

@Benchmark
def groupBy(bh: Blackhole): Unit = {
val result = xs.groupBy(_ % 5)
bh.consume(result)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -78,4 +78,10 @@ class ArrayBufferBenchmark {
@Benchmark
def map(bh: Blackhole): Unit = bh.consume(xs.map(x => x + 1))

@Benchmark
def groupBy(bh: Blackhole): Unit = {
val result = xs.groupBy(_ % 5)
bh.consume(result)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,10 @@ class ListBufferBenchmark {
@Benchmark
def map(bh: Blackhole): Unit = bh.consume(xs.map(x => x + 1))

@Benchmark
def groupBy(bh: Blackhole): Unit = {
val result = xs.groupBy(_ % 5)
bh.consume(result)
}

}
18 changes: 18 additions & 0 deletions src/main/scala/strawman/collection/Iterable.scala
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,21 @@ trait IterableOps[+A, +CC[X], +C] extends Any {
def slice(from: Int, until: Int): C =
fromSpecificIterable(View.Take(View.Drop(coll, from), until - from))

/** Partitions this $coll into a map of ${coll}s according to some discriminator function.
*
* Note: When applied to a view or a lazy collection it will always force the elements.
*
* @param f the discriminator function.
* @tparam K the type of keys returned by the discriminator function.
* @return A map from keys to ${coll}s such that the following invariant holds:
* {{{
* (xs groupBy f)(k) = xs filter (x => f(x) == k)
* }}}
* That is, every key `k` is bound to a $coll of those elements `x`
* for which `f(x)` equals `k`.
*
*/
def groupBy[K](f: A => K): immutable.Map[K, C]

/** Map */
def map[B](f: A => B): CC[B] = fromIterable(View.Map(coll, f))
Expand Down Expand Up @@ -268,6 +283,9 @@ trait Buildable[+A, +C] extends Any with IterableOps[A, AnyConstr, C] {
(l.result(), r.result())
}

def groupBy[K](f: A => K): immutable.Map[K, C] =
generic.GroupBy.strict(f, coll, () => newBuilder)

// one might also override other transforms here to avoid generating
// iterators if it helps efficiency.
}
Expand Down
16 changes: 16 additions & 0 deletions src/main/scala/strawman/collection/Map.scala
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,22 @@ trait MapOps[K, +V, +CC[X, Y] <: Map[X, Y], +C <: Map[K, V]]
*/
def get(key: K): Option[V]

/** Returns the value associated with a key, or a default value if the key is not contained in the map.
* @param key the key.
* @param default a computation that yields a default value in case no binding for `key` is
* found in the map.
* @tparam V1 the result type of the default computation.
* @return the value associated with `key` if it exists,
* otherwise the result of the `default` computation.
*
* @usecase def getOrElse(key: K, default: => V): V
* @inheritdoc
*/
def getOrElse[V1 >: V](key: K, default: => V1): V1 = get(key) match {
case Some(v) => v
case None => default
}

/** Retrieves the value which is associated with the given key. This
* method invokes the `default` method of the map if there is no mapping
* from the given key to a value. Unless overridden, the `default` method throws a
Expand Down
Loading