请关注我的微信公众号
个人微信公众号
技术交流群 (仅作技术交流):642646237
请关注我的头条号:
基本构造单元——筛选——需要根据筛选条件来产生一个子集合的时候,用filter
筛选(filter)是列表的一种基本操作:根据用户定义的条件来筛选列表中的条目,并由此产生一个较小的新列表。
筛选会产生一个新的列表(或集合),其大小根据筛选条件,可能小于原列表。
基本构造单元——筛选——需要根据筛选条件来产生一个子集合的时候,用filter
——Java8版本
首先制造一个从1到目标数字的区间,然后在该区间上施加
filter()
方法,剔除不是约数的数字:Java的取模运算(%)返回整数除法的余数,余数为0即表示除数是被除数的约数。
基本构造单元——筛选——需要根据筛选条件来产生一个子集合的时候,用filter
——Groovy版本
基本构造单元——映射——需要就地变换一个集合的时候,用map
映射(map)操作对原集合的每一个元素执行给定的函数,从而变换成一个新的集合。
基本构造单元——映射——需要就地变换一个集合的时候,用map——groovy版本
java编写的getFactors()
:
使用Groovy改写:
最后调用了unique()方法来消除列表中的重复项,确保完全平方数的平方根(如16的平方根4)不会在列表中出现两次。
基本构造单元——映射——需要就地变换一个集合的时候,用map——Clojure版本
如果让每个函数都合并成一行,那么一系列的函数定义就可以变成一个赋值语句的列表。Clojure的
(let [])
块允许创建一系列作用于仅限于块内的赋值。首先要计算的是目标数的约数,为此需准备从1到目标数的区间(range 1 (inc num))
,其中右端点写成(inc num)
是因为Clojure的区间定义不包括右端点。接着用(filter )
方法消去不需要的集合元素。一般来说,上述语句按照Clojure的习惯写出来应该是(filter #(zero? (rem num %)) (range 1 (inc num)))
,不过既然概念上是先有区间再做筛选,那么让代码的阅读次序和思路保持一致会更好一些。Clojure的thread-last宏(->>
运算符)可以帮我们做这样的次序调整。求得了全部约数之后,就是对sum
和aliquot-sum
的赋值。函数余下部分的工作是逐条判断aliquot-sum满足哪一则条件,并返回相应的关键字(以冒号开头的符号,可以当作枚举来使用)。
折叠/化约——需要把集合分成一小块一小块来处理的时候,用reduce
或fold
第三种基本套路的函数名称最为多样,而且在几种流行语言里的实现各有微妙的区别。
foldLeft
和reduce
都是catamorphism这种范畴论的态射概念具体应用到列表操纵上面的变体,catamorphism是对列表“折叠”(fold)概念的推广。
reduce
和fold
操作在功能上大致重合,但根据具体的编程语言而有微妙的区别。两者都用一个累积量(accumulator)来“收集”集合元素。reduce
函数一般在需要为累积量设定一个初始值的时候使用,而fold
起始的时候累积量是空的。函数在操作集合的时候可以有不同的次序,这点会体现在相应的函数命名上(如foldLeft
和foldRight
)。这里提到的任何一种操作,都不会改变原集合。
“fold left”的含义是:
用一个二元函数或运算符来结合列表的首元素和累积量的初始值(如果累积量有初始值的话);
重复上一步直到列表耗尽,此时累积量的取值即为折叠运算的结果。
这个过程恰好就是对一个数字列表求和的过程:从0开始,加上第一个元素,求得的结果再加上第二个元素,就这样一直进行下去,直到列表元素全部用完。
折叠操作可理解为令每一个列表元素依次结合的一次变换,变换的结果是从整个列表累积成单独的一个值。左折叠按照从左到右的次序结合列表元素,由一个初始值开始,把元素一个接一个地累加上去,最后得到一个结果。
由于加法满足交换律,无论用foldLeft()
还是foldRight()
都将得到同样的结果。但有些运算(包括减法和除法在内)不能随便调换顺序,这时foldRight()
就会派上用场。在纯函数式语言里,左折叠和右折叠的实现并不相同。右折叠允许操作无限长度的列表,而左折叠则不允许。
折叠/化约——需要把集合分成一小块一小块来处理的时候,用reduce或fold——Java8之前的版本使用Functional Java框架
Functional Java框架专为Java 8以前版本的JDK而设计,因此不得不创造性地运用单方法接口和匿名内部类来达到目的。内建的
F2
类正好具备折叠操作所需的结构,用它创建了一个方法,方法的两个参数(将被此方法折叠在一起的两个值)和返回值都为Integer
类型。
折叠/化约——需要把集合分成一小块一小块来处理的时候,用reduce
或fold
——Groovy版本
Groovy的inject
方法第一个参数是初始值,第二个参数是接受两个参数,返回一个值的闭包。
fold
或reduce
常常用在需要从一个集合处理产生另一个大小不同(通常较小但不必然)的集合或单一值的情况。
支持三种基本构造单元的语言会影响代码风格
当项目选用的语言(无论是否函数式语言)支持这些抽象的时候,代码的风格会发生明显的变化。
学习函数式编程,或者任何一种新范式都有一个很大的挑战,那就是在掌握新的构造单元之后,还要善于从问题里“发现”它们的身影,从而抓住解答的脉络。
函数式编程不会用很多抽象,但每个抽象的泛化程度都很高(特化的方面通过高阶函数注入)。
函数式编程以参数传递和函数的复合作为主要的表现手段,我们不需要掌握太多作为“不确定因素”存在的其他语言构造之间的交互规则,这一点对于我们的学习是有利的。
网友评论