使用高阶函数的函数式编程对集合进行操作
Posted 大数据技术学习和分享
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用高阶函数的函数式编程对集合进行操作相关的知识,希望对你有一定的参考价值。
使用高阶函数的函数式编程对集合进行操作
集合元素的映射-map 映射操作
看一个实际需求
要求:请将 List(3,5,7) 中的所有元素都 * 2 ,将其结果放到一个新的集合中返回,即返回一个新的 List(6,10,14), 请编写程序实现
map 映射操作
上面提出的问题,其实是一个关于集合元素的操作的问题 在scala中可以通过map映射操作来解决:将集合中的每一个元素通过指定功能(函数)映射(转换)新的结果集合,这里其实就是所谓的将函数作为参数传递给另外一个函数,这就是函数式编程的特点
以HashSet为例进行说明, def mapB:HashSet[B]//map函数的签名 1)这个就是map映射函数集合类型都有
2)[B]指的是一个范型
3)map是一个高阶函数(可以接收一个函数的函数,就是高阶函数),可以接收函数f:(A)=>B
4)HastSet[B]就是返回的新的集合
高阶函数基本使用案例 1
package com.ldc
object HighOrderFunDemo01 {
def main(args: Array[String]): Unit = {
//使用高阶函数
val res = test(sum2 _, 3.5)
println("res=" + res)
//在 scala 中,可以把一个函数直接赋给一个变量,但是不执行函数
val f1 = myPrint _
f1() //执行
}
def myPrint(): Unit = {
println("hello,world!")
}
//说明
//1. test 就是一个高阶函数
//2. f: Double => Double 表示一个函数, 该函数可以接受一个 Double,返回 Double
//3. n1: Double 普通参数
//4. f(n1) 在 test 函数中,执行 你传入的函数
def test(f: Double => Double, n1: Double) = {
f(n1)
}
//普通的函数, 可以接受一个 Double,返回 Double
def sum2(d: Double): Double = {
println("sum2 被调用")
d + d
}
}
高阶函数应用案例 2
package com.ldc
object HighOrderFunDemo02 {
def main(args: Array[String]): Unit = {
test2(sayOK)
}
//说明 test2 是一个高阶函数,可以接受一个 没有输入,返回为 Unit 的函数
def test2(f: () => Unit) = {
f()
}
def sayOK() = {
println("sayOKKK...")
}
def sub(n1:Int): Unit = {
}
}
使用 map 映射函数来解决
/*请将 List(3,5,7) 中的所有元素都 * 2 ,
将其结果放到一个新的集合中返回,即返回一个新的 List(6,10,14), 请编写程序实现. */
val list = List(3,5,7,9)
//说明 list.map(multiple) 做了什么
// /1. 将 list 这个集合的元素 依次遍历
// 2. 将各个元素传递给 multiple 函数 => 新 Int
// 3. 将得到新 Int ,放入到一个新的集合并返回
// 4. 因此 multiple 函数调用 3
val list2 = list.map(multiple)
println("list2=" + list2) //List(6,10,14)
def multiple(n:Int): Int = {
println ("multiple 被调用~~")
2 * n
}
深刻理解 map 映射函数的机制-模拟实现
package com.ldc
object MapOperateDemo02 {
def main(args: Array[String]): Unit = {
/*请将 List(3,5,7) 中的所有元素都 * 2 ,
将其结果放到一个新的集合中返回,即返回一个新的 List(6,10,14), 请编写程序实现. */
val list = List(3, 5, 7, 9)
//说明 list.map(multiple) 做了什么
// 1. 将 list 这个集合的元素 依次遍历
// 2. 将各个元素传递给 multiple 函数 => 新 Int
// 3. 将得到新 Int ,放入到一个新的集合并返回
// 4. 因此 multiple 函数调用 3
val list2 = list.map(multiple)
println("list2=" + list2) //List(6,10,14)
// 深刻理解 map 映射函数的机制-模拟实现
val myList = MyList()
val myList2 = myList.map(multiple)
println("myList2=" + myList2)
}
def multiple(n: Int): Int = {
println("multiple 被调用~~")
2 * n
}
}
//深刻理解 map 映射函数的机制-模拟实现
class MyList {
val list1 = List(3, 5, 7, 9) //新的集合
var list2 = List[Int]() //写 map
def map(f: Int => Int): List[Int] = {
//遍历集合
for (item <- this.list1) {
//过滤,扁平化。。。
list2 = list2 :+ f(item)
}
list2
}
}
object MyList {
def apply(): MyList = new MyList()
}
flatmap 映射:flat 即压扁,压平,扁平化映射
扁平化说明
flatmap:flat 即压扁,压平,扁平化,效果就是将集合中的每个元素的子元素映射到某个函数并返回新的集合
package com.ldc
object FlatMapDemo01 {
def main(args: Array[String]): Unit = {
val names = List("Alice", "Bob", "Nick")
//需求是将 List 集合中的所有元素,进行扁平化操作,即把所有元素打散
val names2 = names.flatMap(upper)
println("names2=" + names2)
}
def upper( s : String ) : String = {
s. toUpperCase
}
}
集合元素的过滤-filter
基本的说明
filter:将符合要求的数据(筛选)放置到新的集合
案例演示
应用案例:将 val names = List("Alice", "Bob", "Nick") 集合中首字母为'A'的筛选到新的集合。思考:如果这个使用传统的方式,如何完成?
package com.ldc
object FilterDemo01 {
def main(args: Array[String]): Unit = {
/*
选出首字母为 A 的元素
*/
val names = List("Alice", "Bob", "Nick")
val names2 = names.filter(startA)
println("names=" + names)
println("names2=" + names2)
}
def startA(str:String): Boolean = {
str.startsWith("A")
}
}
化简
看一个需求:
val list = List(1, 20, 30, 4 ,5) , 求出 list 的和
化简的介绍:
化简:将二元函数引用于集合中的函数,。上面的问题当然可以使用遍历 list 方法来解决,这里我们使用 scala 的化简方式来完成。
package com.ldc
object ReduceDemo01 {
def main(args: Array[String]): Unit = {
/*使用化简的方式来计算 list 集合的和*/
val list = List(1, 20, 30, 4, 5)
val res = list.reduceLeft(sum)
// reduce/reduceLeft/reduceRight
// 执行的流程分析//步骤 1 (1 + 20)
// 步骤 2 (1 + 20) + 30
// 步骤 3 ((1 + 20) + 30) + 4
// 步骤 4 (((1 + 20) + 30) + 4) + 5 = 60
println("res=" + res) // 60
def sum(n1: Int, n2: Int): Int = {
("sum 被调用~~")
n1 + n2
}
}
}
对 reduceLeft 的运行机制的说明
1) def reduceLeftB >: A => B): B
2) reduceLeft(f) 接收的函数需要的形式为 op: (B, A) => B): B
3) reduceleft(f) 的运行规则是 从左边开始执行将得到的结果返回给第一个参数
4) 然后继续和下一个元素运行,将得到的结果继续返回给第一个参数,继续.. 即: //((((1 + 2) + 3) + 4) + 5) = 15
折叠
基本介绍
fold 函数将上一步返回的值作为函数的第一个参数继续传递参与运算,直到 list 中的所有元素被遍历。
可以把 reduceLeft 看做简化版的 foldLeft。
如何理解:
def reduceLeftB >: A => B): B = if(isEmpty) throw new UnsupportedOperationException("empty.reduceLeft") else tail.foldLeftB(op)
大家可以看到. reduceLeft 就是调用的 foldLeftB,并且是默认从集合的 head 元素开始操作的。
相关函数:fold,foldLeft,foldRight,可以参考 reduce 的相关方法
应用案例
看下面代码看看输出什么,并分析原因. 代码如下:
package com.ldc
object FoldDemo01 {
def main(args: Array[String]): Unit = {
val list = List(1, 2, 3, 4)
def minus(num1: Int, num2: Int): Int = {
num1 - num2
}
//说明
//1. 折叠的理解和化简的运行机制几乎一样. //理解 list.foldLeft(5)(minus) 理解成 list(5,1, 2, 3, 4) list.reduceLeft(minus)
//步骤 (5-1)
//步骤 ((5-1) - 2)
//步骤 (((5-1) - 2) - 3)
//步骤 ((((5-1) - 2) - 3)) - 4 = - 5
println(list.foldLeft(5)(minus)) // 函数的柯里化
////理解 list.foldRight(5)(minus) 理解成 list(1, 2, 3, 4, 5) list.reduceRight(minus)
// 步骤 (4 - 5)
// 步骤 (3- (4 - 5))
// 步骤 (2 -(3- (4 - 5)))
// 步骤 1- (2 -(3- (4 - 5))) = 3
println(list.foldRight(5)(minus)) //
}
}
foldLeft 和 foldRight 缩写方法分别是:/:和:\
package com.ldc
object FlodDemo02 {
def main(args: Array[String]): Unit = {
val list4 = List(1, 9)
def minus(num1: Int, num2: Int): Int = {
num1 - num2
}
var i6 = (1 /: list4) (minus) // =等价=> list4.foldLeft(1)(minus)
println("i6=" + i6)
i6 = (100 /: list4) (minus) //=等价=> list4.foldLeft(100)(minus)
println(i6) // 输出?
i6 = (list4 :\ 10) (minus) // list4.foldRight(10)(minus)
println(i6) // 输出? 2
}
}
扫描
基本介绍
扫描,即对某个集合的所有元素做 fold 操作,但是会把产生的所有中间结果放置于一个集合中保存
package com.ldc
object ScanDemo01 {
def main(args: Array[String]): Unit = {
//普通函数
def minus(num1: Int, num2: Int): Int = {
num1 - num2
}
//5 (1,2,3,4,5) =>(5, 4, 2, -1, -5, -10) //Vector(5, 4, 2, -1, -5, -10)
val i8 = (1 to 5).scanLeft(5)(minus) //IndexedSeq[Int]
println("i8=" + i8) //普通函数
def add(num1: Int, num2: Int): Int = {
num1 + num2
} //(1,2,3,4,5) 5 => (20,19,17,14, 10,5)
val i9 = (1 to 5).scanRight(5)(add) //IndexedSeq[Int]println("i9=" + i9)
}
}
扩展-拉链(合并)
基本介绍
在开发中,当我们需要将两个集合进行 对偶元组合并,可以使用拉链
package com.ldc
object ZipDemo01 {
def main(args: Array[String]): Unit = {
// 拉链
val list1 = List(1, 2, 3)
val list2 = List(4, 5, 6)
val list3 = list1.zip(list2) // (1,4),(2,5),(3,6)
// println("list3=" + list3)
}
}
拉链的使用注意事项
1) 拉链的本质就是两个集合的合并操作,合并后每个元素是一个 对偶元组。
2) 操作的规则下图:
3) 如果两个集合个数不对应,会造成数据丢失。
4) 集合不限于 List, 也可以是其它集合比如 Array5) 如果要取出合并后的各个对偶元组的数据,可以遍历
for(item<-list3){print(item.1 + " " + item.2)} //取出时,按照元组的方式取出
扩展-迭代器
基本说明
通过 iterator 方法从集合获得一个迭代器,通过 while 循环和 for 表达式对集合进行遍历.(学习使用迭代器来遍历)
package com.ldc
object IteratorDemo01 {
def main(args: Array[String]): Unit = {
val iterator = List(1, 2, 3, 4, 5).iterator
// 得到迭代器
//这里我们看看 iterator 的继承关系
/*
def iterator: Iterator[A] = new AbstractIterator[A] {
var these = self
def hasNext: Boolean = !these.isEmpty
def next(): A = if (hasNext) {
val result = these.head;
these = these.tail;
result
} else Iterator.empty.next()*/
println("--------遍历方式 1 while -----------------")
while(iterator.hasNext) {
println(iterator.next())
}
println ("--------遍历方式 2 for -----------------")
for (enum <- iterator) {
println(enum)
}
}
}
}
对代码小结
1) iterator 的构建实际是 AbstractIterator 的一个匿名子类,该子类提供了
/def iterator: Iterator[A] = new AbstractIterator[A] { var these = self def hasNext: Boolean = !these.isEmpty def next(): A =/
2) 该 AbstractIterator 子类提供了 hasNext next 等方法.
3) 因此,我们可以使用 while 的方式,使用 hasNext next 方法
扩展-流 Stream
基本说明
stream 是一个集合。这个集合,可以用于存放无穷多个元素,但是这无穷个元素并不会一次性生产出来,而是需要用到多大的区间,就会动态的生产,末尾元素遵循 lazy 规则(即:要使用结果才进行计算的)
创建 Stream 对象
案例
def numsForm(n: BigInt) : Stream[BigInt] = n #:: numsForm(n + 1)val stream1 = numsForm(1)
说明
1) Stream 集合存放的数据类型是 BigInt
2) numsForm 是自定义的一个函数,函数名是程序员指定的。
3) 创建的集合的第一个元素是 n , 后续元素生成的规则是 n + 1
4) 后续元素生成的规则是可以程序员指定的 ,比如 numsForm( n * 4)..
流的应用案例
//创建 Stream
def numsForm(n: BigInt) : Stream[BigInt] = n #:: numsForm(n + 1)
val stream1 = numsForm(1)
println(stream1) ////取出第一个元素
println("head=" + stream1.head)
println(stream1.tail) // 当对流执行 tail 操作时,就会生成一个新的数据. println(stream1) /
扩展-视图 View
基本介绍
Stream 的懒加载特性,也可以对其他集合应用 view 方法来得到类似的效果,具有如下特点:
1) view 方法产出一个总是被懒执行的集合。
2) view 不会缓存数据,每次都要重新计算,比如遍历 View 时
应用案例
请找到 1-100 中,数字倒序排列 和它本身相同的所有数。(1 2, 11, 22, 33 ...)
package com.ldc
object ViewDemo01 {
def main(args: Array[String]): Unit = {
def multiple(num: Int): Int = {
num
}
//如果这个数,逆序后和原来数相等,就返回 true,否则返回 false
def eq(i: Int): Boolean = {
println("eq 被调用..")
i.toString.equals(i.toString.reverse)
}
//说明: 没有使用 view,常规方式
val viewSquares1 = (1 to 100).filter(eq)
println(viewSquares1)
//使用 view,来完成这个问题,程序中,对集合进行 map,filter,reduce,fold...
// 你并不希望立即执行,而是在使用到结果才执行,则可以使用 view 来进行优化.
val viewSquares2 = (1 to 100).view.filter(eq)
println(viewSquares2)
//遍历
for (item <- viewSquares2) {
println("item=" + item)
}
}
}
扩展-并行集合
1) Scala 为了充分使用多核 CPU,提供了并行集合(有别于前面的串行集合),用于多核环境的并行计算。
2) 主要用到的算法有: Divide and conquer : 分治算法,Scala 通过 splitters(分解器),combiners(组合器)等抽象层来实现,主要原理是将计算工作分解很多任务,分发给一些处理器去完成,并将它们处理结果合并返回Work stealin 算法【学数学】,主要用于任务调度负载均衡(load-balancing),通俗点完成自己的所有任务之后,发现其他人还有活没干完,主动(或被安排)帮他人一起干,这样达到尽早干完的
应用案例
parallel(pærəlel 并行)
(1 to 5).foreach(println(_))
println()
(1 to 5).par.foreach(println(_))
查看并行集合中元素访问的线程
object ParDemo02 {
def main(args: Array[String]): Unit = {
val result1 = (0 to 100).map{case _ => Thread.currentThread.getName}.distinct
val result2 = (0 to 100).par.map{case _ => Thread.currentThread.getName}.distinct
println(result1) //非并行
println("--------------------------------------------")
println(result2) //并行
}
}
扩展-操作符
操作符扩展
1) 如果想在变量名、类名等定义中使用语法关键字(保留字),可以配合反引号反引号 [案例演示] val val
= 42
2) 中置操作符:A 操作符 B 等同于 A.操作符(B)
3) 后置操作符:A 操作符 等同于 A.操作符,如果操作符定义的时候不带()则调用时不能加括号[案例演示+代码说明]
4) 前置操作符,+、-、!、~等操作符 A 等同于 A.unary_操作符 [案例演示]
5) 赋值操作符,A 操作符= B 等同于 A = A 操作符 B ,比如 A += B 等价 A = A +B
代码的演示
object OperatorDemo01 {
def main(args: Array[String]): Unit = {
val n1 = 1
val n2 = 2
val r1 = n1 + n2 // 3
val r2 = n1.+(n2) // 3 看 Int 的源码即可说明
val monster = new Monster
monster + 10
monster.+(10)
println("monster.money=" + monster.money) // 20
println(monster++)
println(monster.++)
println("monster.money=" + monster.money) // 22
!monster
println("monster.money=" + monster.money) // -22
}
}
class Monster {
var money: Int = 0
//对操作符进行重载 (中置操作符)
def +(n:Int): Unit = {
this.money += n
}
//对操作符进行重载(后置操作符)
def ++(): Unit = {
this.money += 1
}
//对操作符进行重载(前置操作符,一元运算符)
def unary_!(): Unit = {
this.money = -this.money
}
}
以上是关于使用高阶函数的函数式编程对集合进行操作的主要内容,如果未能解决你的问题,请参考以下文章
Java高阶进阶之Java函数式编程-Stream流-Lambda表达式
Kotlin函数式编程 ① ( 函数式编程简介 | 高阶函数 | 函数类别 | Transform 变换函数 | 过滤函数 | 合并函数 | map 变换函数 | flatMap 变换函数 )