Scala中的聚合列表值

时间:2009-06-21 13:37:18

标签: scala functional-programming scala-collections

从包含两个参数名义和货币​​的对象列表开始,我如何汇总每种货币的总名义?

假设:

case class Trade(name: String, amount: Int, currency: String)

val trades = List(
  Trade("T150310", 10000000, "GBP"),
  Trade("T150311", 10000000, "JPY"),
  Trade("T150312", 10000000, "USD"),
  Trade("T150313", 100, "JPY"),
  Trade("T150314", 1000, "GBP"),
  Trade("T150315", 10000, "USD")
)

我怎样才能得到:

Map(JPY -> 10000100, USD -> 10010000, GBP -> 10001000)

3 个答案:

答案 0 :(得分:16)

如果使用行李箱,机器已经在那里。 groupBy是在Traversable上定义的,sum可以直接应用到列表中,你不必写一个折叠。

scala> trades groupBy (_.currency) map { case (k,v) => k -> (v map (_.amount) sum) }
res1: Iterable[(String, Int)] = List((GBP,10001000), (JPY,10000100), (USD,10010000))

答案 1 :(得分:4)

我编写了一个简单的分组操作(实际上是Groupable trait,其隐式转化来自Iterable),这样您就可以按currency对您的交易进行分组}:

trait Groupable[V] extends Iterable[V] {
  def groupBy(f: V => K): MultiMap[K, V] = {
    val m = new mutable.HashMap[K, Set[V]] with mutable.MultiMap[K, V]
    foreach { v => m add (f(v), v) } //add is defined in MultiMap
    m
  }
}
implicit def it2groupable(it: Iterable[V]): Groupable[V] = new Groupable[V] {
  def elements = it.elements
}

所以Groupable只是提供了一种方法,可以从Iterable中的每个项目中提取,然后将所有具有相同键的项目分组。所以,在你的情况下:

//mm is a MultiMap[Currency, Trade]
val mm = trades groupBy { _.currency } 

您现在可以做一个非常简单的mapElementsmmMap)和foldLeft(或/: - 非常值得了解{{ 1}}运算符,因为它可以对集合进行非常简洁的聚合)来获得总和:

foldLeft

如果我在最后一行犯了一些错误,请道歉。 val sums: Map[Currency, Int] = mm mapElements { ts => (0 /: ts) { (sum,t) => sum + t.notional } } ts的值,当然是mm

答案 2 :(得分:0)

Scala 2.13开始,大多数集合都提供了groupMapReduce方法,该方法(顾名思义)与groupBy后跟mapValues等效(效率更高)和减少步骤:

trades.groupMapReduce(_.currency)(_.amount)(_ + _)
// immutable.Map[String,Int] = Map(JPY -> 10000100, USD -> 10010000, GBP -> 10001000)

此:

  • group个元素基于其货币( MapReduce的组部分)

  • map的分组值与其数量成组(组 Map Reduce的映射部分)

  • reduce的值(_ + _)通过求和(减少groupMap Reduce 的一部分)。

这是以下列表中的等效版本performed in one pass

trades.groupBy(_.currency).mapValues(_.map(_.amount).reduce(_+_))