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

时间:2017-08-22 05:01:38

标签: scala functional-programming mapreduce monads

我有两个包含案例类对象的列表

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

他们有没有更短的方法呢?

3 个答案:

答案 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的子类型。