我很难理解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
}
我很困惑这个函数的最后一行实际上在做什么? 任何帮助都会很棒。 感谢。
答案 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)
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
。
例如,当chars
为chars
时,左侧表达式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
。)
作为一个整体,例如0
为List('a', 'b', 'a', 'c')
,当chars
转换为列表时,最终映射为List(('a', 2), ('b', 1), ('c', 1))
。