合并两个包含 case class 个对象的列表 scala

Merge two lists which contains case class objects scala

我有两个列表,其中包含 case class 个对象

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))

他们有没有更短的方法来做到这一点?

您实际上不需要创建元组列表。

(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,根据定义,它没有顺序。

另一种方法是为 Balance 添加一个 Semigroup 实例并将其用于 combine 逻辑。这样做的好处是该代码只在一个地方,而不是散布在您需要组合 Balance 的列表或地图的任何地方。

因此,您首先添加实例:

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 returns。如果需要,可以修复该问题。
N.B。如果 Balance.

具有有意义的 empty 值,则您可能希望使用 Monoid 而不是 Semigroup

对于那些需要合并两个案例 class 对象列表,同时保持原始顺序的人,这是我基于 to this question and this 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

这将 return 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 总是 return 以未指定的顺序排列,除非我们专门使用 SortedMap.

的子类型