泛函编程(7)-数据结构-List-折叠算法
折叠算法是List的典型算法。通过折叠算法可以实现众多函数组合(function composition)。所以折叠算法也是泛函编程里的基本组件(function combinator)。了解折叠算法的原理对了解泛函组合有着至关紧要的帮助。折叠算法又可分右折叠和左折叠。我们先从右折叠(foldRight)开始:
从以上两图示可以得出对List(a,b,c)的右折叠算法:op(a,op(b,op(c,z))) 可以看出括号是从右开始的。计算方式如图二:op(a,sub), sub是重复子树,可以肯定要用递归算法。这里z代表了一个起始值。我们现在可以推算出foldRight的函数款式(function signature)了:
1 def foldRight[A,B](l: List[A], z: B)(op: (A,B) => B): B = l match {
2 case Nil => z
3 case Cons(h,t) => op(h,foldRight(t,z)(f))
4 }
注意foldRight不是一个尾递归算法(tail recursive)。我们试着对一个List(1,2,3)进行操作,先来个加法:
1 foldRight(List(1,2,3),0)((x,y) => x + y) //> res13: Int = 6
2 foldRight(List(1,2,3),0){_ + _} //> res14: Int = 6
我们可以用”等量替换“方法简约:
1 // (List(x1,x2,x3...x{n-1}, xn) foldRight acc) op => x1 op (...(xn op acc)...)
2 // foldRight(Cons(1,Cons(2,Cons(3,Nil))), 0) {_ + _}
3 // 1 + foldRight(Cons(2,Cons(3,Nil)), 0) {_ + _}
4 // 1 + (2 + foldRight(Cons(3,Nil), 0) {_ + _})
5 // 1 + (2 + (3 + foldRight(Nil, 0) {_ + _}))
6 // 1 + (2 + (3 + 0)) = 6
1 foldRight(List(1,2,3),1){_ * _} //> res16: Int = 6
2 foldRight(List(1,2,3),Nil:List[Int]) { (a,b) => Cons(a+10,b) }//> res17: ch3.list.List[Int] = Cons(11,Cons(12,Cons(13,Nil)))
注意以上的起始值1和Nil:List[Int]。z的类型可以不是A,所以op的结果也有可能不是A类型,但在以上的加法和乘法的例子里z都是Int类型的。但在List重构例子里z是List[Int]类型,所以op的结果也是List[Int]类型的,这点要特别注意。
再来看看左折叠算法:
从以上图示分析,左折叠算法就是所有List元素对z的操作op。从图二可见,op对z,a操作后op的结果再作为z与b再进行op操作,如此循环。看来又是一个递归算法,而z就是一个用op累积的值了:op(op(op(z,a),b),c)。左折叠算法的括号是从左边开始的。来看看foldLeft的实现:
1 def foldLeft[A,B](l: List[A], acc: B)(op: (B,A) => B): B = l match {
2 case Nil => acc
3 case Cons(h,t) => foldLeft(t,op(acc,h))(op)
4 }
注意z (zero) 变成了 acc (accumulator),op: (B,A) = B, 和foldRight的op函数入参顺序是颠倒的。foldLeft是个尾递归方法。
1 foldLeft(List(1,2,3),0)((b,a) => a + b) //> res18: Int = 6
2 foldLeft(List(1,2,3),0){_ + _} //> res19: Int = 6
3 foldLeft(List(1,2,3),1)((b,a) => a * b) //> res20: Int = 6
4 foldLeft(List(1,2,3),1){_ * _} //> res21: Int = 6
5 foldLeft(List(1,2,3),Nil:List[Int]) { (b,a) => Cons(a+10,b) }
6 //> res22: ch3.list.List[Int] = Cons(13,Cons(12,Cons(11,Nil)))
以上加法和乘法的累积值acc都是A类型,但注意List重构的acc是List[Int]类型的,这个时候op入参的位置就很重要了。再注意一下,foldLeft重构的List的元素排列是反向的Cons(13,Cons(12,Cons(11,Nil))。我们还是可以用“等量替换”方法进行简约:
1 // (List(x1,x2,x3...x{n-1}, xn) foldLeft acc) op => (...(acc op x1) op x2)...) op x{n-1}) op xn
2 // foldLeft(Cons(1,Cons(2,Cons(3,Nil))), 0) {_ + _}
3 // foldLeft(Cons(2,Cons(3,Nil)), (0 + 1)) {_ + _}
4 // foldLeft(Cons(3,Nil), ((0 + 1) + 2)) {_ + _}
5 // foldLeft(Nil, (((0 + 1) + 2) + 3)) {_ + _}
6 // (((0 + 1) + 2) + 3) + 0 = 6
除foldRight,foldLeft之外,折叠算法还包括了:reduceRight,reduceLeft,scanRight,scanLeft。
reduceLeft是以第一个,reduceRight是以最后一个List元素作为起始值的折叠算法,没有单独的起始值:
1 def reduceLeft[A](l: List[A])(op: (A,A) => A): A = l match {
2 case Nil => sys.error("Empty list!")
3 case Cons(h,t) => foldLeft(t,h)(op)
4 }
5 def reduceRight[A](l: List[A])(op: (A,A) => A): A = l match {
6 case Cons(h,Nil) => h
7 case Cons(h,t) => op(h,reduceRight(t)(op))
8 }
1 reduceLeft(List(1,2,3)) {_ + _} //> res23: Int = 6
2 reduceRight(List(1,2,3)) {_ + _} //> res24: Int = 6
scanLeft, scanRight 分别把每次op的结果插入新产生的List作为返回结果。
先实现scanLeft:
1 def scanLeft[A](l: List[A],z: A)(op: (A,A) => A): List[A] = l match {
2 case Nil => Cons(z,Nil)
3 case Cons(h,t) => Cons(z,scanLeft(t,op(z,h))(op))
4 }
1 scanLeft(List(1,2,3),0) {_ + _} //> res25: ch3.list.List[Int] = Cons(0,Cons(1,Cons(3,Cons(6,Nil))))
试试简约:
1 // (List(x1,x2,x3...x{n-1}, xn) scanLeft acc) op => (...(acc op x1) op x2)...) op x{n-1}) op xn2 // scanLeft(Cons(1,Cons(2,Cons(3,Nil))), 0) {_ + _}3 // Cons(0,scanLeft(Cons(1,Cons(2,Cons(3,Nil))), 0) {_ + _})4 // Cons(0,Cons((0 + 1), scanLeft(Cons(2,Cons(3,Nil)), (0 + 1)) {_ + _}))5 // ==> Cons(0,Cons(1,scanLeft(Cons(2,Cons(3,Nil)), 1) {_ + _}))6 // Cons(0,Cons(1,Cons(2 + 1,scanLeft(Cons(3,Nil), 1 + 2) {_ + _})))7 // ==> Cons(0,Cons(1,Cons(3,scanLeft(Cons(3,Nil), 3) {_ + _})))8 // Cons(0,Cons(1,Cons(3,Cons(3 + 3,foldLeft(Nil, 3 + 3) {_ + _}))))9 // ==> Cons(0,Cons(1,Cons(3,Cons(6,foldLeft(Nil, 6) {_ + _}))))
10 // Cons(0,Cons(1,Cons(3,Cons(6,Nil))))
再实现scanRight:
1 def reverse[A](l: List[A]): List[A] = foldLeft(l,Nil:List[A]){(acc,h) => Cons(h,acc)}2 3 def scanRight[A](l: List[A],z: A)(op: (A,A) => A): List[A] = {4 var scanned = List(z)5 var acc = z6 var ll = reverse(l)7 var x = z8 while (9 ll match {
10 case Nil => false
11 case Cons(h,t) => { x = h; ll = t; true }
12 }
13 ) {
14 acc = op(acc,x)
15 scanned = Cons(acc,scanned)
16 }
17 scanned
18 }
实在没能想出用递归算法实现scanRight的方法,只能用while loop来解决了。注意虽然使用了临时变量,但这些变量都是本地封闭的,所以scanRight还是纯函数。scanRight元素遍历(traverse)顺序是反向的,所以用reverse函数把List(1,2,3)先变成List(3,2,1)。
1 scanRight(List(1,2,3),0) {_ + _} //> res26: ch3.list.List[Int] = Cons(6,Cons(5,Cons(3,Cons(0,Nil))))
注意scanRight和scanLeft的结果不同。这是因为算法不同:元素遍历(traverse)顺序不同。
下面开始示范一下折叠算法作为基本组件(combinator)来实现一些函数功能:
上次实现了函数++,即append。我们同样可以用foldLeft和foldRight来实现:
1 def appendByFoldRight[A](l1: List[A], l2: List[A]): List[A] = foldRight(l1,l2){(h,acc) => Cons(h,acc)}
2 def appendByFoldLeft[A](l1: List[A], l2: List[A]): List[A] = foldLeft(reverse(l1),l2){(acc,h) => Cons(h,acc)}
1 appendByFoldLeft(List(1,2),List(3,4)) //> res27: ch3.list.List[Int] = Cons(1,Cons(2,Cons(3,Cons(4,Nil))))
2 appendByFoldRight(List(1,2),List(3,4)) //> res28: ch3.list.List[Int] = Cons(1,Cons(2,Cons(3,Cons(4,Nil))))
由于append的功能是将两个List拼接起来,必须保证最终结果List元素的顺序。所以在appendByFoldLeft里使用了reverse。再注意foldLeft和foldRight在op参数位置是相反的。
之前递归算法实现的函数有些是可以用折叠算法实现的:
1 def map_1[A,B](l: List[A])(f: A => B): List[B] = foldRight(l,Nil: List[B]){(h,acc) => Cons(f(h),acc)}
1 def filter_1[A](l: List[A])(f: A => Boolean): List[A] = foldRight(l,Nil: List[A]){(h,acc) => if (f(h)) Cons(h,acc) else acc }
2 def flatMap_1[A,B](l: List[A])(f: A => List[B]): List[B] = foldRight(l,Nil: List[B]){(h,acc) => appendByFoldRight(f(h),acc)}
1 def lengthByFoldRight[A](l: List[A]): Int = foldRight(l,0){(_,acc) => acc + 1 }
2 def lengthByFoldLeft[A](l: List[A]): Int = foldLeft(l,0){(acc,_) => acc + 1 }
还有些比较间接的:
1 def conCat[A](ll: List[List[A]]): List[A] = foldRight(ll,Nil: List[A]){appendByFoldRight}
这个函数可以用来实现flatMap:
1 def flatMap_1[A,B](l: List[A])(f: A => List[B]): List[B] = conCat(map(l)(f))
如果理解以上函数实现方式有困难时可以先从类型匹配上下手,或者试着用“等量替换”方法简约跟踪一下。
泛函编程(7)-数据结构-List-折叠算法相关推荐
- Interview之AI:人工智能领域岗位求职面试—人工智能算法工程师知识框架及课程大纲(AI基础之数学基础/数据结构与算法/编程学习基础、ML算法简介、DL算法简介)来理解技术交互流程
Interview之AI:人工智能领域岗位求职面试-人工智能算法工程师知识框架及课程大纲(AI基础之数学基础/数据结构与算法/编程学习基础.ML算法简介.DL算法简介)来理解技术交互流程 目录 一.A ...
- C、C++以及widows编程、数据结构算法等编程书籍推荐
推荐几本好书么 当然,FAQ的目的之一就是这个.我将分别介绍纯C.C++以及widows编程.数据结构算法以及图形库等等的教材.市场上这些主题中任意一个的教材都是汗牛充栋,读都读不完.不过很显 ...
- C语言链表的转置算法,c语言编程集 数据结构 顺序表 点链表 数制转换 矩阵转置.doc...
c语言编程集 数据结构 顺序表 点链表 数制转换 矩阵转置 #include "stdio.h" #include "malloc.h" /*typedef s ...
- 【从蛋壳到满天飞】JS 数据结构解析和算法实现-AVL树(一)
前言 [从蛋壳到满天飞]JS 数据结构解析和算法实现,全部文章大概的内容如下: Arrays(数组).Stacks(栈).Queues(队列).LinkedList(链表).Recursion(递归思 ...
- python数据结构推荐书-「算法与数据结构」从入门到进阶吐血整理推荐书单
推荐一下「算法与数据结构」从入门到进阶的书单. 一.入门系列 这些书籍通过图片.打比方等通俗易懂的方法来讲述,让你能达到懂一些基础算法,线性表,堆栈,队列,树,图,DP算法,背包问题等,不要求会实现, ...
- Algorithm:【Algorithm算法进阶之路】之数据结构二十多种算法演示
Algorithm:[Algorithm算法进阶之路]之数据结构二十多种算法演示 目录 一.数据结构算法 1.顺序表 2.链表 3.栈和队列 4.串的模式匹配 5.稀疏矩阵 6.广义表 7.二叉树 8 ...
- 牛人整理分享的面试知识:操作系统、计算机网络、设计模式、Linux编程,数据结构总结...
网站地址:http://www.itmian4.com 基础篇:操作系统.计算机网络.设计模式 一:操作系统 1. 进程的有哪几种状态,状态转换图,及导致转换的事件. 2. 进程与线程的区别. 3. ...
- 「数据结构」普林斯顿算法课第二周作业
「数据结构」普林斯顿算法课第二周作业 Algorithm I, Princeton 编程作业: Deques and Randomized Queues 思路 Deque.java Randomize ...
- 程序员编程艺术:面试和算法心得
本文转载至:http://taop.marchtea.com/ 本书是July和他伙伴们的<程序员编程艺术>的电子书 <程序员编程艺术:面试和算法心得> 目录 第一部分 数据结 ...
- 基于可编程图形处理器的骨骼动画算法及其比较
2009-10-19 作者: 季卓尔 张景峤 来源: 万方数据 关键字: 骨骼动画 可编程图形处理器 计算机图形 蒙皮网格 统一 骨骼动画相对于之前的动画方法具有占用空间小的优点,但是其代价是计算量的 ...
最新文章
- 【iOS系列】-程序开启后台运行
- 动态规划实现斐波那契数列求解
- matlab程序崩溃怎么解决,跪求matlab达人帮我看看程序,我实在是要崩溃了......
- operator new在C++中的各种写法
- Java牛客专项练习2020.12.10
- pyQt显示系统文件目录
- Flume Sink Processor
- 引用管理器没有程序集_Excel结构化表与名称管理器
- js图表报表html5图表,介绍一个!非常简单非常方便!制作图表报表的js库 —— ichartjs 【详细操作】...
- 简介如何查看执行计划以及执行计划的准确性
- mysql innodb 索引组织表_Mysql InnoDB引擎 -索引组织表
- 保证可见性_十年程序员浅谈并发的原子性、可见性、有序性
- Django之jsonp跨域请求原理
- 机器学习之CART树
- vs2010生成的exe更改icon
- 【Matlab】系统预定义变量——元胞数组与结构体
- python微信图片dat转码(整理版)
- php中各种报错处理,PHP中的异常和错误处理
- VC调试程序输出First-chance Exception.
- python str与bytes之间的转换
热门文章
- 使用HTML5构建iOS原生APP(2)
- HDU 2553 N皇后问题 DFS 简单题
- 常用数据类型使用转换详解
- 微信支付 第一次成功,其他无法调起,返回-1(Android eclipse 微信支付之大坑 签名工具问题)
- Entity Framework Plus
- WampServer服务中MySQL无法正常启动解决方案
- box-shadow用法;http://riny.net/lab/css3/box-shadow.html
- 功不唐捐,每日一道LeetCode,玉汝于成,终获offer满满!
- 天善SVIP五折狂欢!今年双十一终于不用剁手了
- tidyverse —— readr包