题目概述
折叠,也称为归约(reduce)、积累(accumulate)、聚集(aggregate)、压缩(compress)或注入(inject),指称一组高阶函数,它们分析递归数据结构并通过使用给定组合运算,将递归的处理它的构成部件、建造一个返回值的结果重组起来。典型的,要向折叠提供一个组合函数,一个数据结构的顶端节点,和可能的在特定条件下使用的某些缺省值。折叠接着以系统性方式使用这个函数,进行组合这个数据结构的层级中的元素 – By wiki
下面的Scala
代码定义了右折叠。因为不是尾递归的函数,无法应用对应优化,在as很大的时候会造成Stack-Overflow。我们需要探索stack-safe的实现。
1 | def foldRight[A, B](as: List[A], z:B)(f: (A, B) => B): B = |
解题报告
注意到,实现尾调用的foldLeft是很trivial的,有没有什么办法利用foldLeft呢?
1 | def foldLeft[A, B](as: List[A], z:B)(f: (B, A) => B): B = |
一个朴素的想法是借助reverse
通过foldLeft实现foldRight,如果reverse是stack-safe的,且foldLeft也是stack-safe的,那么我们就有了stack-safe的foldRight实现。代码如下:
1 | def foldRightViafoldLeft_1[A, B](as: List[A], z:B)(f: (A, B) => B): B = |
Hard:如果不使用reverse呢?首先我们先观察以下例子,对于list: [1, 2, 3]:
1 | foldLeft(list, z)(f) ==> f(f(f(z, 1), 2), 3) |
第一阶段:对于两个结果如果实现互换,那么f和g的映射中有文章。foldLeft和foldRight是否可以逐步转换?
1 | f(z, 1) ==> g(1, z) |
第二阶段:如果z是一个computed value我们很难做到这一点,但如果是一个function呢?也就是:
1 | z => f(z, 1) ==> z => g(1, z) // z => f(z, 1) 与 f(identity[B], 1) 等价 |
第三阶段:令z:B,我们可以定义f为:
1 | 令 f(a: A, b: B): B |
最后阶段:我们可以利用foldLeft做文章,令f(a: A, b: B): B; foldLeft(list, b:B => b)((g, a) => x => g(f(a, x)))将产生一个function,其signature为B => B。然后我们对其apply初始值就能满足最终要求:
1 | def foldRightViafoldLeft_2[A, B](as: List[A], z:B)(f: (A, B) => B): B = |
可以跟朴素实现对比观察,可以看出来规律:
1 | def foldRightViafoldLeft_1[A, B](as: List[A], z:B)(f: (A, B) => B): B = |
其实foldRightViafoldLeft_2与foldRightViafoldLeft_1思路一致,都是要同时调换f的运算顺序和列表顺序。foldRightViafoldLeft_2通过构造function而不是computed value将两个对调放在一起了。两个函数在compose的时候,外层的函数后计算,内层的先计算。再结合currying,不断partial apply并compose,就能得到与朴素实现一模一样的结果。