原文链接 http://www.iteblog.com/archives/1228
从本质上说,fold函数将一种格式的输入数据转化成另外一种格式返回。
fold, foldLeft和foldRight这三个函数除了有一点点不同外,做的事情差不多。
我将在下文解释它们的共同点并解释它们的不同点。
我将从一个简单的例子开始,用fold计算一系列整型的和。
1 |
val numbers = List( 5 , 4 , 8 , 6 , 2 ) |
2 |
numbers.fold( 0 ) { (z, i) = > |
List中的fold方法需要输入两个参数:初始值以及一个函数。输入的函数也需要输入两个参数:累加值和当前item的索引。
那么上面的代码片段发生了什么事?
// scala 2.10.5
// package scala.collection
// trait LinearSeqOptimized
override /*TraversableLike*/
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
}
由上面的代码可知,传入的函数需要两个参数,第一个参数是累加值,
第二个可以看作是一个普通的参数,只是每次都是these.head,并不是“当前item的索引”。
代码开始运行的时候,初始值0作为第一个参数传进到fold函数中,list中的第一个item作为第二个参数传进fold函数中。
1、fold函数开始对传进的两个参数进行计算,在本例中,仅仅是做加法计算,然后返回计算的值;
2、Fold函数然后将上一步返回的值作为输入函数的第一个参数,并且把list中的下一个item作为第二个参数传进继续计算,同样返回计算的值;
3、第2步将重复计算,直到list中的所有元素都被遍历之后,返回最后的计算值,整个过程结束;
4、这虽然是一个简单的例子,让我们来看看一些比较有用的东西。
早在后面将会介绍foldLeft函数,并解释它和fold之间的区别,
目前,你只需要想象foldLeft函数和fold函数运行过程一样。
下面是一个简单的类和伴生类:
1 |
class Foo( val name : String, val age : Int, val sex : Symbol) |
4 |
def apply(name : String, age : Int, sex : Symbol) = new Foo(name, age, sex) |
假如我们有很多的Foo实例,并存在list中:
1 |
val fooList = Foo( "Hugh Jass" , 25 , 'male) :: |
2 |
Foo("Biggus Dickus", 43, ' male) :: |
3 |
Foo( "Incontinentia Buttocks" , 37 , 'female) :: |
我们想将上面的list转换成一个存储[title] [name], [age]格式的String链表:
01 |
val stringList = fooList.foldLeft(List[String]()) { (z, f) = > |
02 |
val title = f.sex match { |
06 |
z : + s "$title ${f.name}, ${f.age}" |
和第一个例子一样,我们也有个初始值,这里是一个空的String list,也有一个操作函数。
在本例中,我们判断了性别,并构造了我们想要的String,并追加到累加器中(这里是一个list)。
fold, foldLeft, and foldRight之间的区别
主要的区别是fold函数操作遍历问题集合的顺序。
foldLeft是从左开始计算,然后往右遍历。
foldRight是从右开始算,然后往左遍历。
而fold遍历的顺序没有特殊的次序。
来看下这三个函数的实现吧(在TraversableOnce特质里面实现)
01 |
def fold[A 1 > : A](z : A 1 )(op : (A 1 , A 1 ) = > A 1 ) : A 1 = foldLeft(z)(op) |
03 |
def foldLeft[B](z : B)(op : (B, A) = > B) : B = { |
05 |
this .seq foreach (x = > result = op(result, x)) |
09 |
def foldRight[B](z : B)(op : (A, B) = > B) : B = |
10 |
reversed.foldLeft(z)((x, y) = > op(y, x)) |
由于fold函数遍历没有特殊的次序,所以对fold的初始化参数和返回值都有限制。
在这三个函数中,初始化参数和返回值的参数类型必须相同。
第一个限制是初始值的类型必须是list中元素类型的超类。
在我们的例子中,我们的对List[Int]进行fold计算,而初始值是Int类型的,它是List[Int]的超类。
第二个限制是初始值必须是中立的(neutral)。也就是它不能改变结果。
比如对加法来说,中立的值是0;而对于乘法来说则是1,对于list来说则是Nil。
顺便说下,其实foldLeft和foldRight函数还有两个缩写的函数:
1 |
def / : [B](z : B)(op : (B, A) = > B) : B = foldLeft(z)(op) |
3 |
def : [B](z : B)(op : (A, B) = > B) : B = foldRight(z)(op) |
5 |
scala> ( 0 / : ( 1 to 100 ))( _ + _ ) |
8 |
scala> (( 1 to 100 ) :
0 )( _ + _ ) |
def testFold = {
println("test foldLeft")
val t = 1 to 5 toList
val r1 = t.fold(1016)((z, head) => { println(head); z - head })
println(r1)
println("test foldRight")
val r2 = t.foldRight(1016)((head, z) => { println(head); z - head })
println(r2)
/**
* test foldLeft
* 1
* 2
* 3
* 4
* 5
* 1001
* test foldRight
* 5
* 4
* 3
* 2
* 1
* 1001
*/
}