文章目录
Map 映射的操作应用
1. 实际需求
一个实际需求:请将 List(3, 5, 7) 中的所有元素都 * 2,将其结果放到一个新的集合中返回,即返回一个新的 List(6, 10, 14) 。
传统方法代码如下:
object MapOperateDemo01 {
def main(args: Array[String]): Unit = {
val list1 = List(3, 5, 7)
var list2 = List[Int]()
for (item <- list1) list2 = list2 :+ item * 2
println(list2) //List(6, 10, 14)
}
}
优点:
- 处理比较直接,思路简单
缺点:
- 不够简洁高效
- 没有体现函数实编程的特点,集合=>函数=>新的集合=>函数…
- 不利于处理复杂的数据处理业务
上面提出一个问题,其实就是一个关于集合元素映射操作的问题。在Scala中可以通过map映射操作来解决:将集合中的每一个元素通过指定功能(函数)映射(转换)成新的结果集合,这里其实就是所谓的作为参数传递给另外一个函数,这是函数式编程的特点。
以 HashSet 为例说明:
def map[B](f:(A) => B): HashSet[B] //map函数的签名
- map是一个高阶函数(可以接受一个函数的函数,就是高阶函数),可以接受函数 f:(A) => B 。
- HashSet[B] 就是返回的新的集合 。
- [B] 是泛型 。
2. 高阶函数解决实际需求
代码演示:
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, Scala")
}
/*
说明:
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
}
}
运行结果:
sum2被调用
res: 7.0
Hello, Scala
Process finished with exit code 0
注意事项:
- 1.上述代码中,val f1 = myPrint _ ,在Scala中,可以把一个函数直接赋值给一个变量,但是不执行该函数,函数名 _ 。赋值给变量 f1 ,f1() 是调用该函数。
- 2.test就是一个高阶函数, f:Double=>Double ,表示一个函数,该函数可以接受一个 Double,返回 Double。n1:Double 表示普通参数。f(n1) 在test函数中,执行传入的函数。
3. 使用 map 映射来解决
依旧是上述需求案例,使用map映射来解决。
代码演示:
object MapOperateDemo02 {
def main(args: Array[String]): Unit = {
/*
请将 List(3, 5, 7) 中的元素都 * 2
将其结果放到一个新的集合中返回,即返回一个新的 List(6, 10, 14)
*/
val list = List(3, 5, 7)
val list2 = list.map(multiple)
println("list2=" + list2)
}
def multiple(n: Int): Int = {
println("multiple 被调用!")
2 * n
}
}
multiple 被调用!
multiple 被调用!
multiple 被调用!
list2=List(6, 10, 14)
Process finished with exit code 0
注意事项:上述代码中 val list2 = list.map(multiple) 做了什么?
- 1.将 list 这个集合的元素依次遍历。
- 2.将各个元素传递给 multiple 函数 => 新 Int
- 3.将得到的新 Int,放入一个新的集合并且返回。
- 4.因此,multiple 函数被调用了3次。如上运行结果显示。
4. 模拟实现 map 函数机制
在上面的代码基础上编写如下代码:
object MapOperateDemo02 {
def main(args: Array[String]): Unit = {
/*
请将 List(3, 5, 7) 中的元素都 * 2
将其结果放到一个新的集合中返回,即返回一个新的 List(6, 10, 14)
*/
val list = List(3, 5, 7)
val list2 = list.map(multiple)
println("list2=" + list2)
//深刻理解 map 映射函数的机制——模拟实现
val myList = MyList()
val myList2 = myList.map(multiple)
println("myList2: " + myList2)
}
def multiple(n: Int): Int = {
println("multiple 被调用!")
2 * n
}
}
class MyList {
val list1 = List(3, 5, 7, 9)
var list2 = List[Int]()
def map(f: Int => Int): List[Int] = {
//遍历集合
for (item <- this.list1) {
list2 = list2 :+ f(item)
}
list2
}
}
//伴生对象
object MyList {
def apply(): MyList = new MyList()
}
multiple 被调用!
multiple 被调用!
multiple 被调用!
list2=List(6, 10, 14)
multiple 被调用!
multiple 被调用!
multiple 被调用!
multiple 被调用!
myList2: List(6, 10, 14, 18)
Process finished with exit code 0
5. flatmap 映射(扁平化)
flatmap:flat即压扁,压平,扁平化,效果就是将集合中的每个元素的子元素映射到某个函数并返回新的集合。
代码演示:
object FlatmapDemo01 {
def main(args: Array[String]): Unit = {
val names = List("Alice", "Bob", "Nick")
val names2 = names.flatMap(upper)
println(names2)
}
def upper(s: String): String = {
s.toUpperCase()
}
}
List(A, L, I, C, E, B, O, B, N, I, C, K)
Process finished with exit code 0
集合元素过滤 filter
基本说明:
filter:将符合要求的数据(筛选)放置到新的集合中。
案例:将 val names = List(“Alice”, “Bob”, “Nick”) 集合当中首字母为 ‘A’ 的筛选到新的集合。
代码演示:
object FilterDemo01 {
def main(args: Array[String]): Unit = {
val names = List("Alice", "Bob", "Nick")
val names2 = names.filter(startA)
println(names2)
}
def startA(s: String): Boolean = {
s.startsWith("A")
}
}
List(Alice)
Process finished with exit code 0
化简(reduceLeft,reduceRight,reduce)
案例需求:
val list = List(1, 20, 30, 4, 5) 求出 list 的和。
化简介绍:
将二元函数引用于集合中的函数。上述需求可以遍历list来解决,这里我们使用scala的化简方式来完成。
代码演示:
object ReduceDemo01 {
def main(args: Array[String]): Unit = {
/*
使用化简的方式来计算list集合的和
*/
val list = List(1, 20, 30, 4, 5)
/*
执行流程:
1. (1 + 20)
2. ((1 + 20) + 30)
3. (((1 + 20) + 30) + 4)
4. ((((1 + 20) + 30) + 4) + 5) === 60
*/
val res = list.reduceLeft(sum) //调用了4次sum
println(res)
}
def sum(n1:Int, n2:Int): Int = {
println("调用sum")
n1 + n2
}
}
运行结果如下:
调用sum
调用sum
调用sum
调用sum
60
Process finished with exit code 0
对 reduceLeft 的运行机制说明:
- reduceLeft(f) 的运行原则是,从左边开始执行得到的结果返回给第一个参数,然后继续和下一个元素运行,将得到的结果继续返回给第一个参数,继续…
- reduceLeft(f) 接受的函数需要的形式为 op:(B, A) => B): B
案例代码:
object ReduceExercise01 {
def main(args: Array[String]): Unit = {
val list = List(1, 2, 3, 4, 5)
def minus(num1: Int, num2: Int) = {
num1 - num2
}
println(list.reduceLeft(minus)) // (((1-2)-3)-4)-5 = -13
println(list.reduceRight(minus)) // 1-(2-(3-(4-5))) = 3
println(list.reduce(minus)) //等价于 reduceLeft
//求最小值
println(list.reduceLeft(min)) //1
}
def min(n1:Int, n2:Int): Int = {
if (n1>n2) n2 else n1
}
}
-13
3
-13
1
Process finished with exit code 0
在上述代码中,注意 reduceLeft 和 reduceRight 的区别,reduce的效果和reduceLeft一样。
折叠(foldLeft,foldRight)
说明:
fold 函数将上一步返回的值,作为函数的第一个参数继续传递参数与运算,直到list中的所有元素被遍历。
可以把 reduceLeft 看作简化版的 foldLeft。如何理解?
可以看到,reduceLeft就是调用的foldLeft[B] ,并且是默认从集合的head元素开始操作的。
相关函数:fold,foldLeft,foldRight,可以参考 reduce 的相关方法理解。
折叠案例代码:
object FoldDemo01 {
def main(args: Array[String]): Unit = {
val list = List(1, 2, 3, 4)
def minus(num1:Int, num2:Int): Int = {
num1 - num2
}
println(list.foldLeft(5)(minus)) //函数柯里化
/*
柯里化(Currying)指的是将原来接受两个参数的函数变成新的接受一个参数的函数的过程。
新的函数返回一个以原有第二个参数为参数的函数。
list.foldLeft(5)(minus) 意思是在list最左侧添加一个5,即 List(5, 1, 2, 3, 4)
然后执行 list.reduceLeft(minus)
执行步骤:
1. (5-1)
2. ((5-1)-2)
3. (((5-1)-2)-3)
4. ((((5-1)-2)-3)-4) = -5
*/
println(list.foldRight(5)(minus))
/*
list.foldRight(5)(minus) 意思是 List(1, 2, 3, 4, 5)
然后执行 list.reduceRight(minus)
执行步骤:
1. (4-5)
2. (3-(4-5))
3. (2-(3-(4-5)))
4. (1-(2-(3-(4-5)))) = 3
*/
}
}
foldLeft 和 foldRight 缩写方法分别是 /: 和 :\
代码演示:
object FoldDemo02 {
def main(args: Array[String]): Unit = {
val list = List(1, 9)
def minus(num1:Int, num2:Int): Int = {
num1 - num2
}
var res = (1 /: list)(minus) // 等价于: list.foldLeft(1)(minus)
println(res) // -9
var res_ = (list :\ 10)(minus) // 等价于: list.foldRight(10)(minus)
println(res_) // 2
}
}
-9
2
Process finished with exit code 0
扫描(scanLeft,scanRight)
扫描,即对某个集合的所有元素进行fold操作,但是会把产生的所有中间结果放置于一个集合中保存。
代码演示:
object ScanDemo01 {
def main(args: Array[String]): Unit = {
def minus(num1:Int, num2:Int): Int = {
num1 - num2
}
val res = (1 to 5).scanLeft(5)(minus)
println(res) //Vector(5, 4, 2, -1, -5, -10)
/*
5 (1 ,2, 3, 4, 5)
(5, 4, 2, -1, -5, -10)
*/
def add(num1:Int, num2:Int): Int = {
num1 + num2
}
val res_ = (1 to 5).scanRight(5)(add)
println(res_) //Vector(20, 19, 17, 14, 10, 5)
/*
(1, 2, 3, 4, 5) 5
(20, 19, 17 , 14 ,10 ,5)
*/
}
}
综合案例
案例1:
val sentence = “AAAAAAAAAABBBBBBBBCCCCCDDDDDDD” 将sentence中各个字符,通过foldLeft存放到一个 ArrayBuffer 中,理姐 foldLeft的用法。
ArrayBuffer(‘A’, ‘A’, ‘A’…)
代码演示:
import scala.collection.mutable.ArrayBuffer
object Exercise01 {
def main(args: Array[String]): Unit = {
val sentence = "AAAAAAAAAABBBBBBBBCCCCCDDDDDDD"
val arrayBuffer = new ArrayBuffer[Char]()
sentence.foldLeft(arrayBuffer)(putArray)
println(arrayBuffer)
}
def putArray(arr:ArrayBuffer[Char], c:Char): ArrayBuffer[Char] = {
//将c放入到arr中
arr.append(c)
arr
}
}
ArrayBuffer(A, A, A, A, A, A, A, A, A, A, B, B, B, B, B, B, B, B, C, C, C, C, C, D, D, D, D, D, D, D)
Process finished with exit code 0
案例2:
“AAAAAAAAAABBBBBBBBCCCCCDDDDDDD”
使用映射集合,统计一句话中,各个字母出现的次数。
Java代码演示:
import java.util.HashMap;
import java.util.Map;
public class Exercise02_java {
public static void main(String[] args) {
String str = "AAAAAAAAAABBBBBBBBCCCCCDDDDDDD";
Map<Character, Integer> charCountMap = new HashMap<>();
System.out.println(charCountMap);
char[] cs = str.toCharArray();
System.out.println(cs);
for (char c : cs) {
if (charCountMap.containsKey(c)) { //查询Map中是否包含指定的key
Integer count = charCountMap.get(c);
charCountMap.put(c, count+1);
} else {
charCountMap.put(c, 1);
}
}
System.out.println(charCountMap);
}
}
{}
AAAAAAAAAABBBBBBBBCCCCCDDDDDDD
{A=10, B=8, C=5, D=7}
Process finished with exit code 0
scala foldLeft 折叠代码实现:
import scala.collection.mutable
object Exercise02 {
def main(args: Array[String]): Unit = {
val sentence = "AAAAAAAAAABBBBBBBBCCCCCDDDDDDD"
val map2 = sentence.foldLeft(Map[Char,Int]())(charCount)
println(map2)
val map3 = mutable.Map[Char, Int]()
sentence.foldLeft(map3)(charCount2)
println(map3)
}
//使用不可变 Map 实现
def charCount(map: Map[Char, Int], char: Char): Map[Char, Int] = {
map + (char -> (map.getOrElse(char, 0) + 1))
}
//使用可变 Map 实现,效率更高
def charCount2(map: mutable.Map[Char, Int], char: Char): mutable.Map[Char, Int] = {
map += (char -> (map.getOrElse(char, 0) + 1))
}
}
Map(A -> 10, B -> 8, C -> 5, D -> 7)
Map(D -> 7, A -> 10, C -> 5, B -> 8)
Process finished with exit code 0
拉链(zip 合并)
在开发中,我们需要将俩个集合进行对偶元组合并,可以使用拉链。
案例代码:
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)
println(list3)
for (item <- list3) println(item._1 + " " + item._2)
}
}
List((1,4), (2,5), (3,6))
1 4
2 5
3 6
Process finished with exit code 0
注意事项:
- 1.拉链的本质就是俩个集合的合并操作,合并后每个元素是一个 对偶元组。
- 2.操作规则如下:
- 3.如果俩个集合个数不对应,会造成数据丢失。
- 4.集合不限于 List,也可以是其它集合比如 Array。
- 5.如果要取出合并后的各个对偶元组的数据,可以遍历。
- for (item <- list3) println(item._1 + " " + item._2)
迭代器 iterator
迭代器 iterator 方法从集合获得一个迭代器,通过 while 循环和 for 表达式对集合进行遍历。
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()
代码演示:
object IteratorDemo01 {
def main(args: Array[String]): Unit = {
val iterator = List(1, 2, 3, 4, 5).iterator //得到迭代器
println("----------遍历方式1:while-------------")
while (iterator.hasNext) {
println(iterator.next())
}
println("----------遍历方是2:for---------------")
for (enum <- iterator) {
println(enum)
}
}
}
----------遍历方式1:while-------------
1
2
3
4
5
----------遍历方是2:for---------------
Process finished with exit code 0
总结:
- 1.iterator的构建实际上是 AbstractIterator 的一个匿名子类,该子类提供了:
- 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) …
代码演示:
object StreamDemo01 {
def main(args: Array[String]): Unit = {
def numsForm(n: BigInt): Stream[BigInt] = n #::numsForm(n+1)
val stream1 = numsForm(1)
println(stream1)
//取出第一个元素
println("head=" + stream1.head)
println(stream1.tail)
println(stream1)
}
}
Stream(1, ?)
head=1
Stream(2, ?)
Stream(1, 2, ?)
Process finished with exit code 0
视图 View(懒加载)
基本介绍:
Stream的懒加载特性,也可以对其他集合应用 view 方法来得到类似的效果,具有如下特点:
- 1.view方法产出一个总是被懒执行的集合。
- 2.view不会缓存数据,每次都要重新计算,比如遍历View时。
案例代码:
object ViewDemo01 {
def main(args: Array[String]): Unit = {
//如果这个数,逆序后和原来的数相等,就返回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)
}
def mutiple(num: Int): Int = {
num
}
}
运行结果如下:在viewSquares2中,并没有加载数据。只有使用的时候才会加载数据。
Vector(1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 22, 33, 44, 55, 66, 77, 88, 99)
SeqViewF(...)
Process finished with exit code 0
在开发中,我们对于集合的操作,并不希望立即执行,而是使用到结果的时候才执行,这个时候应该考虑懒加载,可以使用view来进行优化。
并行集合(par)
基本介绍:
- Scala为了充分使用多核CPU,提供了并行集合(有别于前面的串行集合),用于多核环境的并行计算。
- 主要用到的算法有:
- Divide and conquer:分治算法,Scala通过 splitters(分解器),**combiners(组合器)**等抽象层来实现,主要原理是将计算工作分解成很多任务,分发给一些处理器去完成,并将它们处理结果合并返回。
- Work stealin 算法,主要用于任务调度负载均衡(load-balancing),通俗点完成自己的所有任务后,发现其他人还有活没干完,主动(或被安排)帮他人一起干,这样达到尽快干完的目的。
应用案例(1):
object ParDemo01 {
def main(args: Array[String]): Unit = {
(1 to 5).foreach(println(_))
println()
(1 to 5).par.foreach(println(_))
}
}
运行结果
1
2
3
4
5
4
2
1
3
5
Process finished with exit code 0
应用案例(2):
object ParDemo02 {
def main(args: Array[String]): Unit = {
//Thread.currentThread().getName 获取线程名, distinct输出不同种类的结果
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) //1种
println("-----------------------------------------------")
println(result2) //8种
}
}
Vector(main)
-----------------------------------------------
ParVector(ForkJoinPool-1-worker-13, ForkJoinPool-1-worker-15, ForkJoinPool-1-worker-3, ForkJoinPool-1-worker-5, ForkJoinPool-1-worker-9, ForkJoinPool-1-worker-7, ForkJoinPool-1-worker-1, ForkJoinPool-1-worker-11)
Process finished with exit code 0
如上结果显示,并行计算,我的电脑CPU是8核。
操作符
介绍:
- 如果想在变量名、类名等定义中使用语法关键子(保留字),可以配合反引号(比如: val `val` = 42)。
- 中置操作符:A 操作符 B 等同于 A.操作符(B)
- 后置操作符:A 操作符 等同于 A.操作符 ,如果操作符定义的时候不带(),则调试用时不能加括号【案例演示+代码说明】
- 前置操作符,+、-、!、~ 等操作符 A 等同于 A.unary_操作符
- 赋值操作符,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
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
}
}
monster.money=20
()
()
monster.money=22
monster.money=-22
Process finished with exit code 0