Scala 中的聚合列表值
-
06-07-2019 - |
题
从包含名义和货币两个参数的对象列表开始,如何聚合每种货币的名义总金额?
鉴于:
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)
解决方案
我写了一个简单的分组操作(实际上是 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 }
你现在可以做一个非常简单的 mapElements
(mm
是一个 Map
)和一个 foldLeft
(或者 /:
- 非常值得理解 foldLeft
运算符,因为它可以对集合进行极其简洁的聚合)以获得总和:
val sums: Map[Currency, Int] = mm mapElements { ts =>
(0 /: ts) { (sum,t) => sum + t.notional }
}
如果我在最后一行犯了一些错误,我深表歉意。 ts
的值是 mm
, ,(当然) Iterable[Trade]
.
其他提示
如果你使用后备箱,机器已经在那里了。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))
开始 Scala 2.13
, ,大多数集合都提供了 组MapReduce 方法(顾名思义)相当于(更有效) groupBy
其次是 mapValues
和减少步骤:
trades.groupMapReduce(_.currency)(_.amount)(_ + _)
// immutable.Map[String,Int] = Map(JPY -> 10000100, USD -> 10010000, GBP -> 10001000)
这:
group
s 元素基于其货币(组的一部分 团体映射减少)map
s 分组值到它们的数量(映射组的一部分地图减少)reduce
s 值(_ + _
)通过对它们求和(减少 groupMap 的一部分减少).
这是一个等效版本 一次性执行 通过以下列表:
trades.groupBy(_.currency).mapValues(_.map(_.amount).reduce(_+_))
不隶属于 StackOverflow