在Scala中弃左操作?

时间:2016-07-03 18:49:05

标签: scala function functional-programming fold

我很难理解Scala中的折叠是如何工作的。

以下代码计算列表chars中每个唯一字符的数量    它发生的时间。例如,调用

   times(List('a', 'b', 'a'))

应返回以下内容(结果列表的顺序并不重要):

 List(('a', 2), ('b', 1))


 def times(chars: List[Char]): List[(Char, Int)] = {
  def incr(acc: Map[Char,Int], c: Char) = {
    val count = (acc get c).getOrElse(0) + 1
    acc + ((c, count));
  }
  val map = Map[Char, Int]()
  (map /: chars)(incr).iterator.toList
}

我很困惑这个函数的最后一行实际上在做什么? 任何帮助都会很棒。 感谢。

4 个答案:

答案 0 :(得分:1)

我以更冗长的方式重写了你的功能:

def times(chars: List[Char]): List[(Char, Int)] = {
  chars
    .foldLeft(Map[Char, Int]()){ (acc, c) => 
       acc + ((c, acc.getOrElse(c, 0) + 1))
    }
    .toList
}

让我们看看times("aba".toList)

的第一步

第一次调用:

(Map(), 'a') => Map() ++ Map(`a` -> 1)

第二次调用:

(Map(`a` -> 1), `b`) => Map('a' -> 1) ++ Map('b' ->1)

第三次调用:

(Map('a' -> 1, 'b' ->1), 'a') => 
      Map('a' -> 1, 'b' ->1) ++ Map('a' -> 2) => 
      Map('a' -> 2, 'b' ->1)

答案 1 :(得分:1)

scala代码库中的实际实现非常简洁:

def foldLeft[B](z: B)(f: (B, A) => B): B = {
    var acc = z
    var these = this 
    while (!these.isEmpty) {
      acc = f(acc, these.head)
      these = these.tail
    }
    acc
  }

让我为了清晰起见重命名:

def foldLeft[B](initialValue: B)(f: (B, A) => B): B = {
    //Notice that both accumulator and collectionCopy are `var`s! They are reassigned each time in the loop.
    var accumulator = initialValue
    //create a copy of the collection
    var collectionCopy = this //the function is inside a collection class, so **this** is the collection
    while (!collectionCopy.isEmpty) {
      accumulator = f(accumulator , collection.head)
      collectionCopy = these.tail
    }
    accumulator 
  }

评论后编辑:

让我们现在重新审视OP的功能并以命令式方式重写它(即非功能性,显然是混乱的根源): (map /: chars)(incr)完全等同于chars.foldLeft(map)(incr),可以强制重写为:{/ p>

 def foldLeft(initialValue: Map[Char,Int])(incrFunction: (Map[Char,Int], Char) => Map[Char,Int]): Map[Char,Int] = {
        //Notice that both accumulator and charList are `var`s! They are reassigned each time in the loop.
        var accumulator = initialValue
        //create a copy of the collection
        var charList: List[Char] = this //the function is inside a collection class, so **this** is the collection
        while (!charList.isEmpty) {
          accumulator = incrFunction(accumulator , collection.head)
          charList = these.tail
        }
        accumulator 
      }

我希望这会使foldLeft的概念更加清晰。

因此它本质上是对一个命令式while循环的抽象,它通过遍历集合和更新累加器来累积一些值。累加器使用用户提供的函数进行更新,该函数获取累加器的先前值和集合的当前项。

它的描述暗示它是一个很好的工具来计算集合上的各种聚合,比如sum,max等等。是的,scala集合实际上提供了所有这些函数,但它们是一个很好的示例用例。 / p>

关于你的问题的具体细节,我要指出这可以使用groupBy轻松完成:

def times(l: List[Char]) = l.groupBy(c => c).mapValues(_.size).toList

times(List('a','b','a')) // outputs List[(Char, Int)] = List((b,1), (a,2))

.groupBy(c => c)为您提供Map[Char,List[Char]] = Map(b -> List(b), a -> List(a, a))

然后我们使用.mapValues(_.size)将地图的值映射到分组的子集合的大小:Map[Char,Int] = Map(b -> 1, a -> 2)

最后,您将地图转换为包含.toList的键值元组列表,以获得最终结果。

最后,如果你不像你所说的那样关心输出列表的顺序,那么将输出保留为Map[Char,Int]会更好地传达这个决定(而不是将其转换为列表)。 / p>

答案 2 :(得分:1)

scala中的

foldLeft的工作原理如下:

假设你有一个整数列表,

val nums = List(2, 3, 4, 5, 6, 7, 8, 9, 10)
val res= nums.foldLeft(0)((m: Int, n: Int) => m + n)

你会得到res = 55。

让它可视化。

val res1 = nums.foldLeft(0) { (m: Int, n: Int) => println("m: " + m + " n: " + n); 
m + n }
m: 0 n: 1
m: 1 n: 2
m: 3 n: 3
m: 6 n: 4
m: 10 n: 5
m: 15 n: 6
m: 21 n: 7
m: 28 n: 8
m: 36 n: 9
m: 45 n: 10

所以,我们可以看到我们需要在foldLeft参数中传递初始累加器值。积累的价值存储在' m'我们进入下一个价值' n'。 最后我们得到了累加器。

答案 3 :(得分:1)

让我们从"最后一行"开始您要问的问题:Map特征extends Iterable反过来extends Traversable,其中运算符/:为{{3} },代码(map /: chars)(incr)chars上向左折叠,acc umulator的初始值为从字符到整数的空map ping,应用{{1每个incr的中间值和acc的每个元素c

例如,当charschars时,左侧表达式List('a', 'b', 'a', 'c')等于(map /: chars)(incr)

现在,关于incr(incr(incr(incr(Map[Char, Int](), 'a'), 'b'), 'a'), 'c')的作用:它从字符到整数以及字符incr进行中间映射acc,并按c递增整数对应于映射中的1。 (严格来说,映射是不可变的,因此永远不会发生变异:相反,会创建并返回一个新的,更新的映射。另外,c表示,如果getOrElse(0)中不存在c ,要递增的整数被视为acc。)

作为一个整体,例如0List('a', 'b', 'a', 'c'),当chars转换为列表时,最终映射为List(('a', 2), ('b', 1), ('c', 1))