在Scala中使用累加器映射列表的功能方法

时间:2017-10-30 03:26:31

标签: scala functional-programming

我想编写简洁的代码来映​​射列表,在我去的时候累积一个值并在输出列表中使用该值。

使用递归函数和模式匹配这很简单(见下文)。但是我想知道是否有办法使用组合函数编程系列,如map和fold等。显然map和fold是没有用的,除非你使用在调用之外定义的可变变量并在体内修改它。 / p>

也许我可以通过State 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
  }
}

3 个答案:

答案 0 :(得分:4)

您也可以使用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,)

答案 1 :(得分:3)

这可以通过扫描完成:

val result = list.scanLeft(0){case (acc, item) => acc+item}

扫描将初始值0包含在输出中,因此您必须删除它:

result.drop(1)

答案 2 :(得分:2)

正如@Nyavro's answer中所指出的,您正在寻找的操作(列表前缀的总和)称为prefix-sum,并且它对任何二进制操作的泛化称为scan并且是included in the Scala standard library

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是一种通用迭代方法。你可以通过遍历集合来完成 Everything ,你可以使用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中,foldLeftfoldRight可以执行任何操作,而fold仅限于关联操作。)

或者不同的观点:集合是指令流,可以是EMPTY指令,也可以是ELEMENT(value)指令。 foldLeft / foldRight是该指令集的骨架解释器,作为程序员,您可以提供解释这两个指令的实现,即{{{{ 1}} / foldLeft 对这些说明的解释。

请记住:当foldRight / foldLeft将集合缩减为单个值时,该值可能是任意复杂的,包括作为集合本身!