我有两个包含案例类对象的列表
case class Balance(id: String, in: Int, out: Int)
val l1 = List(Balance("a", 0, 0), Balance("b", 10, 30), Balance("c", 20, 0))
val l2 = List(Balance("a", 10, 0), Balance("b", 40, 0))
我想总结元组中的元素并组合下面的列表
List((Balance(a, 10, 0), Balance(b, 50, 30), Balance(c, 20, 0))
我来了以下解决方案
// create list of tuples with 'id' as key
val a = l1.map(b => (b.id, (b.in, b.out)))
val b = l2.map(b => (b.id, (b.in, b.out)))
// combine the lists
val bl = (a ++ b).groupBy(_._1).mapValues(_.unzip._2.unzip match {
case (ll1, ll2) => (ll1.sum, ll2.sum)
}).toList.map(b => Balance(b._1, b._2._1, b._2._2))
// output
// List((Balance(a, 10, 0), Balance(b, 50, 30), Balance(c, 20, 0))
他们有没有更短的方法呢?
答案 0 :(得分:3)
您实际上不需要创建元组列表。
(l1 ++ l2).groupBy(_.id)
.mapValues(_.foldLeft((0,0)){
case ((a,b),Balance(id,in,out)) => (a+in,b+out)})
.map{
case (k,(in,out)) => Balance(k,in,out)}
.toList
// res0: List[Balance] = List(Balance(b,50,30), Balance(a,10,0), Balance(c,20,0))
您会注意到,由于中间表示为Map
,结果显示无序,根据定义,该结果没有订单。
答案 1 :(得分:1)
另一种方法是为Semigroup
添加Balance
实例,并将其用于 combine 逻辑。这样做的好处是代码只在一个地方,而是在你需要组合Balance
s的列表或地图的地方撒了。
所以,首先添加实例:
import cats.implicits._
implicit val semigroupBalance : Semigroup[Balance] = new Semigroup[Balance]
{
override def combine(x: Balance, y: Balance): Balance =
if(x.id == y.id) // I am arbitrarily deciding this: you can adapt the logic to your
// use case, but if you only need it in the scenario you asked for,
// the case where y.id and x.id are different will never happen.
Balance(x.id, x.in + y.in, x.out + y.out)
else x
}
然后,组合多个列表的代码变得更简单(使用您的示例数据):
(l1 ++ l2).groupBy(_.id).mapValues(_.reduce(_ |+| _)) //Map(b -> Balance(b,50,30), a -> Balance(a,10,0), c -> Balance(c,20,0))
N.B。正如@jwvh已经注意到的那样,在这个简单的情况下,结果将不合适,因为Map
返回的默认无序groupBy
。如果需要,可以修复
注:如果您对Monoid
有一个有意义的Semigroup
值,则可能需要使用empty
而不是Balance
。
答案 2 :(得分:0)
对于那些需要合并两个案例类对象列表,同时又保持原始顺序的人,这是我的解决方案,基于此问题的jwvh's answer和此answer。
import scala.collection.immutable.SortedMap
val mergedList: List[Balance] = l1 ++ l2
val sortedListOfBalances: List[Balance] =
SortedMap(mergedList.groupBy(_.id).toSeq:_*)
.mapValues(_.foldLeft((0,0)){
case ((a,b),Balance(id,in,out)) => (a+in,b+out)
})
.map{
case (k,(in,out)) => Balance(k,in,out)
}
.toList
这将返回List(Balance(a,10,0), Balance(b,50,30), Balance(c,20,0))
,而当不使用SortedMap
时,我们得到List(Balance(b,50,30), Balance(a,10,0), Balance(c,20,0))
。
map
始终以未指定的顺序返回,除非我们专门使用SortedMap
的子类型。