[英]Functional way to map over a list with an accumulator in Scala
我想编写简洁的代码来映射列表,在我去的时候累积一个值并在输出列表中使用该值。
使用递归函数和模式匹配这很简单(见下文)。 但我想知道是否有办法使用组合函数编程系列,如map和fold等。显然map和fold是没有用的,除非你使用在调用之外定义的可变变量并在体内修改它。
也许我可以用状态Monad做到这一点但是想知道是否有一种方法可以做到这一点,我缺少了,并且利用了Scala标准库。
// accumulate(List(10, 20, 20, 30, 20))
// => List(10, 30, 50, 80, 100,)
def accumulate(weights : List[Int], sum : Int = 0, acc: List[Int] = List.empty) : List[Int] = {
weights match {
case hd :: tl =>
val total = hd + sum
accumulate(tl, total, total :: acc)
case Nil =>
acc.reverse
}
}
您也可以使用foldLeft
:
def accumulate(seq: Seq[Int]) =
seq.foldLeft(Vector.empty[Int]) { (result, e) =>
result :+ result.lastOption.getOrElse(0) + e
}
accumulate(List(10, 20, 20, 30, 20))
// => List(10, 30, 50, 80, 100,)
这可以通过扫描完成:
val result = list.scanLeft(0){case (acc, item) => acc+item}
扫描将初始值0包含在输出中,因此您必须删除它:
result.drop(1)
正如在@ Nyavro的回答中指出的那样,您正在寻找的操作(列表前缀的总和 )称为前缀和 ,它对任何二进制操作的泛化称为scan
,并包含在Scala标准库中 :
val l = List(10, 20, 20, 30, 20)
l.scan(0) { _ + _ }
//=> List(0, 10, 30, 50, 80, 100)
l.scan(0)(_ + _).drop(1)
//=> List(10, 30, 50, 80, 100)
这已经得到了回答,但我想在你的问题中解决一个误解:
显然,除非你使用在调用之外定义的可变变量并在体内修改它,否则map和fold是没有用的。
事实并非如此。 fold
是一种通用的迭代方法。 你可以通过遍历集合来做所有事情 ,你可以做fold
。 如果fold
是List
类中唯一的方法,你仍然可以做你现在可以做的一切。 以下是如何使用fold
解决您的问题:
l.foldLeft(List(0)) { (list, el) ⇒ list.head + el :: list }.reverse.drop(1)
并且scan
的一般实现:
def scan[A](l: List[A])(z: A)(op: (A, A) ⇒ A) =
l.
drop(1).
foldLeft(List(l.head)) { (list, el) ⇒ op(list.head, el) :: list }.
reverse
可以这样想:集合可以是空的也可以不是。 fold
有两个参数,一个告诉它在列表为空时该怎么做,另一个告诉它在列表不为空时该怎么做。 这是唯一的两个案例,因此每个可能的案件都得到处理。 因此, fold
可以做任何事情! (更准确地说,在Scala中, foldLeft
和foldRight
可以执行任何操作,而fold
仅限于关联操作。)
或者不同的观点:集合是指令流,可以是EMPTY
指令,也可以是ELEMENT(value)
指令。 foldLeft
/ foldRight
是该指令集的骨架解释器 ,作为程序员,您可以提供对这两个指令的解释的实现,即foldLeft
/ foldRight
的两个参数是对这些指令的解释。
请记住:虽然foldLeft
/ foldRight
将集合缩减为单个值,但该值可以是任意复杂的,包括作为集合本身!
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.