我试图理解Foldable
cats
尝试一个简单的例子foldM
:假设我需要在运行总和为正时对列表中的数字求和,并在运行总和为正时中断。
val sumUp: (Int, Int) => Option[Int] = (x, y) => {
println(s"x = $x, y = $y")
val sum = x + y
if (sum > 0) Some(sum) else None
}
scala> val xs = List(1, 2, 3, -2, -5, 1, 2, 3)
xs: List[Int] = List(1, 2, 3, -2, -5, 1, 2, 3)
scala> Foldable[Stream].foldM(xs.toStream, 0)(sumUp)
x = 0, y = 1
x = 1, y = 2
x = 3, y = 3
x = 6, y = -2
x = 4, y = -5
res27: Option[Int] = None
现在我需要编写新的函数sumUp2
来获取输入流尾巴,它从运行总和变为 <= 0 并且foldM
中断的地方开始。例如,我需要得到这样的东西:
scala> val tail = Foldable[Stream].foldM(xs.toStream, 0)(sumUp2)
tail: Stream[Int] = Stream(-5, ?)
scala>tail.toList
res28: List[Int] = List(-5, 1, 2, 3)
怎么写sumUp2
?
你可以做的是累积两个值(在元组中(:运行总和,直到它变成负数或零;和尾巴,然后开始累积值。
val sumUp2: ((Int, List[Int]), Int) => Id[(Int, List[Int])] = (x, y) => {
val sum = if (x._1 < 0) x._1 else x._1 + y
if (sum > 0) (sum, x._2) else (-1, x._2 ++ List(y))
}
然后,您可以从元组中的第二个元素获取尾巴:
val xs = List(1, 2, 3, -2, -5, 1, 2, 3)
val res = Foldable[Stream].foldM(xs.toStream, (0, List[Int]()))(sumUp2)
println(res._2)
在这里摆弄。
我写了sumUp2
返回Either[Int, (Int, Int)]
:左边是访问元素的数量,右边是一对访问的元素数和运行总和。
type IntOr[A] = Either[Int, A]
val sumUp2: ((Int, Int), Int) => IntOr[(Int, Int)] = (pair, y) => {
val (size, x) = pair
val sum = x + y
println(s"sum = $sum, y = $y")
if (sum > 0) (size + 1, sum).asRight else size.asLeft
}
我们知道,当sumUp2
返回Left
时,foldM
停止,因此不会为所有元素调用sumUp2
:
scala> val r = Foldable[Stream].foldM(xs.toStream, (0, 0))(sumUp2)
sum = 1, y = 1
sum = 3, y = 2
sum = 6, y = 3
sum = 4, y = -2
sum = -1, y = -5
r: IntOr[(Int, Int)] = Left(4)
给定r: Either[Int, (Int, Int)]
我们可以得到尾巴:
scala> r match { case Right(_) => Nil; case Left(n) => xs.drop(n) }
res63: List[Int] = List(-5, 1, 2, 3)
该解决方案似乎工作正常,但对我来说看起来并不好。你会如何改进它?