list scala 当前位置 遍历_【Scala笔记——道】Scala List 遍历 foldLeft / foldRight详解...

本文详细介绍了Scala中List遍历的两种高阶函数foldLeft和foldRight。通过实例展示了它们的使用,包括求和、阶乘等操作。重点解释了foldLeft如何通过尾递归优化避免栈溢出,以及两者在不同场景下的适用性,如foldLeft适用于大表和幂等操作,foldRight适合小表和非幂等操作。
摘要由CSDN通过智能技术生成

HOF foldLeft / foldRight

foldLeft 和 foldRight 都是对于 List 遍历的 高阶函数。是对列表遍历过程中进行函数操作的高阶函数抽象。

List 遍历

假设有两个方法如下

// 求和

def sum(ints: List[Int]): Int = ints match {

case Nil => 0

case Cons(x, xs) => x + sum(xs)

}

//阶乘

def product(ds: List[Double]): Double = ds match {

case Nil => 1.0

case Cons(x, xs) => x * product(xs)

}

可以看到这两个方法体很相似,基本可以概括为

case Nil=> #ReturnBack#

case Cons(head, tail) => #递归遍历 + function 操作#

foldRight

定义输入 参数如下

li : List [A]

b: Nil返回结果

f : ( a : A, b: B) => B 函数操作

将这种方式进行抽象为如下流程

foldLeft简单的 流程可以理解为如下

从列表末尾开始执行函数操作 f, 具体 为 f0 = f ( li [li.lenth -1] , b), 此时 f0 为改末尾节点执行函数结果

在列表上一节点执行函数, 此时 的b 为一节点的函数执行结果,具体为 f1 = f ( li [li.lenth -2] , b = f0)

一直递归到列表头

具体实现如下

def foldRight[A, B](li : List[A], b : B)(f: (A, B)=> B) : B = li match {

case Nil => b

case Cons(head, tail) => f(head, foldRight(tail, b)(f))

}

foladRight 实际上是先对列表进行递归,并将每个节点执行的方法先压入栈中,到最后拿到 b 后再从栈中一步恢复执行。

foldLeft

foladRight中我们的递归操作实际上是依靠栈实现的,但这就会造成一个问题:在列表过大时,会造成栈溢出。

如何解决 栈溢出 的问题?我们比较容易想到的一个办法是 尾递归进行优化

foldRight 相当于从尾部进行递归操作,而栈的引入主要是保存 首次递归到尾部时,之前节点执行 函数操作的状态。

这就是foldLeft 的主要思想

具体实现如下

def foldLeft[A, B](li : List[A], b: B)(f: (B, A)=> B) : B = li match {

case Nil => b

case Cons(head, tail) => foldLeft(tail, f(b, head))(f)

}

实际上foldLeft其实是可以通过尾递归进行优化,因此foldLeft在大List情况下,并不会产生栈溢出情况

再看foldRight和foldLeft

再回到最初的程序,具体的foladRight和foldLeft实现如下

def sumFoldRight(ns: List[Int]) : Int ={

foldRight(ns, 0)((x, y) => x + y)

}

def producetFoldRight(ns : List[Double]): Double = {

foldRight(ns, 1.0)(_*_)

}

def sumFoldLeft(ns: List[Int]): Int = {

foldLeft(ns, 0)(_+_)

}

def produceFoldLeft(ns: List[Double]): Double = {

foldLeft(ns, 1.0)(_*_)

}

看起来 foldRight 和 foldLeft 对于这两种方法实现都是没有问题,但是实际上 foldRight 和 foldLeft 实际上是不同的。

foldRight 的从头遍历实际上我们理解的从末尾开始执行函数,但foldLeft实际上并不是,我们在foldLeft计算时实际上是把List的头当作尾部计算。

这里会带来一个问题相同的 执行函数 在foldLeft和 foldRight可能执行结果并不同。这两者相同的条件相当于我们从头开始函数计算或者从未开始函数计算对结果并没有影响。由于 加法和乘法都满足交换率,因此在这里是没有问题的。换句话说,foldLeft并不能直接替换foldRight,除非操作函数是幂等的

这里我们写一个 int2String方法

def int2StringFoldRight[A](as: List[A]): String = {

foldRight(as, "-")(_+_)

}

def int2StringFoldLeft[A](as: List[A]): String = {

foldLeft(as, "-")(_+_)

}

def main(args: Array[String]): Unit = {

val list = Cons(1, Cons(2, Cons(3, Cons(4, Cons(5, Cons(6, Nil))))))

println(int2StringFoldRight(list))

println("dropLastFoldRight\n")

println(int2StringFoldLeft(list))

println("int2StringFoldLeft\n")

}

执行结果如下

123456-

dropLastFoldRight

-123456int2StringFoldLeft

这样很明显就可以看出才非幂等函数里边,foldRight 和 foldLeft 可以进行替换,并且建议使用 foldLeft 通过尾递归进行优化,但对于非幂等函数,需要慎重使用foldLeft,更建议使用foldRight。

foldLeft适用场景

大表

幂等

foldRight适用场景

小表

非幂等

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值