在 foldLeft 中使用 case class 作为累加器的更优雅的方法是什么?

What would be a more elegant way of using case class as accumulator in foldLeft?

我有一个案例class:

case class Nums(num1: Int, num2: Int)

以及如下列表:

val listOfNums = List(Nums(1,1), Nums(2,2))

我想return Nums(3,3)这是所有相关字段的总和。我实现这一目标的方法是:

listOfNums.foldLeft(Nums(0,0))((acc,nums) => {
  acc.copy(
    num1 = acc.num1 + nums.num1,
    num2 = acc.num2 + nums.num2
  )
})

但是这样感觉有点笨拙,请问怎样才是正确的做法呢?

实际上您要查找的是 Reduce,而不是 fold。你可以试试:

listOfNums.reduce((num1, num2) => Nums(num1.num1 + num2.num1, num1.num2 + num2.num2))

如果你确实想使用折叠,你不必复制,你可以只创建一个新的Nums:

listOfNums.foldLeft(Nums(0,0))((acc,nums) => {
  Nums(
    num1 = acc.num1 + nums.num1,
    num2 = acc.num2 + nums.num2
  )
})

您可以进一步阅读 difference between foldLeft and reduceLeft in Scala

代码 运行 可以在 Scastie 找到。

正如@Thilo 在评论中提到的,您可以将 + 添加到 Nums:

case class Nums(num1: Int, num2: Int) {
  def +(that: Nums): Nums = {
    Nums(num1 + that.num1, num2 + that.num2)
  }
}

然后 foldreduce 用法变得非常明显:

listOfNums.reduce(_ + _)
listOfNums.foldLeft(Nums(0,0))(_ + _)

代码 运行 在另一个 Scastie

如果您愿意使用 cats
然后,您可以简单地为 nums 定义一个 Monoid 这样您只需要编写一次样板文件,然后通过使用 combineAll 或 [=15= 等辅助功能在多个部分重复使用它]

import cats.kernel.Monoid
final case class Nums(num1: Int, num2: Int)
object Nums {
  implicit final val NumsMonoid: Monoid[Nums] =
    new Monoid[Nums] {
      override final val empty: Nums =
        Nums(num1 = 0, num2 = 0)
      
      override def combine(n1: Nums, n2: Nums): Nums =
        Nums(num1 = n1.num1 + n2.num1, num2 = n1.num2 + n2.num2)
    }
}

然后可以像这样使用:

import cats.syntax.all._
val listOfNums = List(Nums(1,1), Nums(2,2))
val result = listOfNums.combineAll
// result: Nums = Nums(3, 3)

可以看到代码运行 here.