Aggregate Listenwerte in Scala
-
06-07-2019 - |
Frage
Beginnend mit einer Liste von Objekten, die zwei Parameter fiktiv und Währung, wie kann ich die Nominal pro Währung aggregieren?
Gegeben:
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")
)
Wie kann ich:
Map(JPY -> 10000100, USD -> 10010000, GBP -> 10001000)
Lösung
Ich schrieb eine einfache Gruppe-by-Betrieb (eigentlich ein Groupable
trait
mit einer impliziten Umwandlung von einem Iterable
), die Sie gruppieren Sie Ihre Trades durch ihre currency
erlauben würde:
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
}
So Groupable
ist einfach ein Weg, um eine Schlüssel zu extrahieren von jedem Element in einem Iterable
und dann Gruppierung alle diese Elemente, die den gleichen Schlüssel haben. Also, in Ihrem Fall:
//mm is a MultiMap[Currency, Trade]
val mm = trades groupBy { _.currency }
Sie können nun eine ganz einfache mapElements
tun (mm
ist ein Map
) und eine foldLeft
(oder /:
- lohnt sich das Verständnis der foldLeft
Betreiber, da es extrem kurze Aggregationen über Sammlungen ermöglicht), um die Summe zu erhalten:
val sums: Map[Currency, Int] = mm mapElements { ts =>
(0 /: ts) { (sum,t) => sum + t.notional }
}
Entschuldigt, wenn ich habe ein paar Fehler in der letzten Zeile gemacht. ts
sind die Werte von mm
, die (natürlich) Iterable[Trade]
sind.
Andere Tipps
Wenn Sie Stamm verwenden die Maschinen sind schon da. groupBy ist definiert auf Travers und Summe kann auf die Liste direkt angewendet werden, Sie müssen nicht eine Falte schreiben.
scala> trades groupBy (_.currency) map { case (k,v) => k -> (v map (_.amount) sum) }
res1: Iterable[(String, Int)] = List((GBP,10001000), (JPY,10000100), (USD,10010000))
Ab Scala 2.13
sind die meisten Sammlungen mit der groupMapReduce Verfahren die (wie der Name schon sagt) ein Äquivalent (effizienteren) eines groupBy
gefolgt von mapValues
und einen Schritt verringern:
trades.groupMapReduce(_.currency)(_.amount)(_ + _)
// immutable.Map[String,Int] = Map(JPY -> 10000100, USD -> 10010000, GBP -> 10001000)
Dieses:
-
group
s Elemente auf der Grundlage ihrer Währung (Gruppe Teil von Gruppe MapReduce) -
map
s gruppiert Werte in ihrer Höhe (Karte Teil der Gruppe Karte Verkleinern) -
reduce
s Wert (_ + _
) von ihnen Summieren (reduzieren Teil groupMap reduzieren ).
Dies ist eine äquivalente Version durchgeführt in einem durch~~POS=TRUNC durch die Liste der:
trades.groupBy(_.currency).mapValues(_.map(_.amount).reduce(_+_))