【问题标题】:Merge two lists which contains case class objects scala合并两个包含案例类对象的列表
【发布时间】:2017-08-22 05:01:37
【问题描述】:

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

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

他们有更短的方法吗?

【问题讨论】:

    标签: scala functional-programming mapreduce monads


    【解决方案1】:

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

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

    【讨论】:

      【解决方案2】:

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

      所以,你首先添加实例:

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

      注意正如@jwvh 已经指出的那样,在这个简单的情况下,结果将不是有序的,因为groupBy 返回默认的无序Map。如果需要,这可以修复。
      注:如果Balance 具有有意义的empty 值,您可能希望使用Monoid 而不是Semigroup

      【讨论】:

        【解决方案3】:

        对于那些需要合并两个案例类对象列表,同时保持原始顺序的人,这是我的解决方案,它基于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 的子类型。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2023-03-22
          • 2019-02-20
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多