第四讲 Scala函数式编程(下)
一、Scala集合体系
(一)、Scala集合体系概述
1、Scala中的集合体系主要包括:Iterable、Seq(IndexSeq)、Set(SortedSet)、Map(SortedMap)。其中Iterable是所有集合trait的根trait。实际上Seq、Set、和Map都是子trait。
Seq:是一个有先后次序的值的序列,比如数组或列表。IndexSeq允许我们通过整形的下标快速的访问任意元素。举例来说,ArrayBuffer是带下标的,但是链表不是。
Set:是一组没有先后次序的值。在SortedSet中,元素以某种排过序顺序被访问。
Map:是一组(键、值)对偶。SortedMap按照键的排序访问其中的实体。
2、 Scala中的集合是分成可变和不可变两类集合的,其中可变集合就是说,集合的元素可以动态修改,而不可变集合的元素在初始化之后,就无法修改了。分别对应scala.collection.mutable和scala.collection.immutable两个包。
3、Seq下包含了Range、ArrayBuffer、List等子trait。其中Range就代表了一个序列,通常可以使用“1 to 10”这种语法来产生一个Range。 ArrayBuffer就类似于Java中的ArrayList。
不可变集合的继承体系
可变集合继承体系
(二)Seq
1、Seq简介
Seq:是一个有先后次序的值的序列,比如数组或列表。IndexSeq允许我们通过整形的下标快速的访问任意元素。举例来说,ArrayBuffer是带下标的,但是链表不是。
这里我们使用List来进行说明。
List是一个这样的集合,是由首元素(head,是这个list的方法)和其余元素组成的子List(tail)构成,除此以外,List还提供了比如isEmpty的基本方法。
一个空列表,通常使用Nil来表示,或者来创建。
构建List,因为是一个trait不能直接创建对象,需要使用其伴生对象来进行。
2、Seq案例
(1)入门案例
object _01ListOps {
def main(args: Array[String]): Unit = {
val list = Nil //空的List列表
val list1 = List[Int](1, 2, 3, 4, 5)//因为List是抽象类,所以只能使用其伴生对象创建对象
println(list.isEmpty)
println(list1.isEmpty)
val first = list1.head
val tail = list1.tail
println("first: " + first)
println("tail: " + tail)
val sum = recursiveList(list1)
println("sum: " + sum)
}
//练习题:使用递归方式,来求解一个List中元素的和
def recursiveList(list: List[Int]): Int = {
if(list.isEmpty)
0
else
list.head + recursiveList(list.tail)
}
}
(2)crud操作
object _02ListOps {
def main(args: Array[String]): Unit = {
//不可变的列表
val left = List[Int](1, 2, 8, 4, 5)
val right = List[Int](6, 7, 8, 9, 10)
//增 --->使用符号操作符(+\-\:)
var ret = left.+:(6)//.+:给list首部添加一个元素,生成一个新的List
println("left.+:(6)==>" + ret)
ret = left.::(6)//.::给list首部添加一个元素,生成一个新的List
println("left.::(6)==>" + ret)
ret = left.:+(6)//.:+给list尾部添加一个元素,生成一个新的List
println("left.:+(6)==>" + ret)
println("-----------新增一个集合------------")
ret = left.++:(right)//++:给list首部添加一个集合,生成一个新的List
println("left.++:(right)==>" + ret)
ret = left.:::(right)//:::给list首部添加一个集合,生成一个新的List
println("left.:::(right)==>" + ret)
ret = left.++(right)//++给list尾部添加一个集合,生成一个新的List
println("left.++(right)==>" + ret)
//删
println("-----delete删除操作--------------------")
val newList = ret.dropWhile((num:Int) => num < 6)
println("ret.dropWhile((num:Int) => num < 6)==>" + newList)
//改
//不可变的元素,不可以修改
//查
println("-------------get查操作--------------------")
val third = left(3)
println("left(3): " + third)
//长度
val size = left.size
println("size: " + size)
//判断
println("-------------判断操作--------------------")
//isEmpty
//contains(ele) 是否包含某一个元素
println(left.contains(3))
//遍历
println("-------------遍历操作--------------------")
//使用for循环遍历
// for(num <- left)
ret.foreach((num:Int) => {
println(num)
})
//高阶函数操作呗
println("-------------高阶函数操作--------------------")
val distinctList = ret.distinct
println("去重之后的List: " + distinctList)
ret = ret.reverse
println("反转之后的List:" + ret)
val takeN = ret.take(3)//take(n)获取集合中的前n个元素,如果集合是有序的,take(n)--->TopN
println("ret.take(3): " + takeN)
ret = left.union(right)//sql中的union all
println("left.union(right)==>" + ret)
}
}
(三)Set
1、Set简介
Set,是一组没有先后次序的值。在SortedSet中,元素以某种排过序顺序被访问。默认情况下,set集合中的元素不可重复,试图将一个重复元素添加到set中是徒劳的。
2、Set操作
不可变set集合
object ImmutSetDemo extends App{
val set = Set(1, 2, 3)
println(set)
//后面的括号可以省略
val set1 = new HashSet[Int]()
val set1 = new HashSet[Int]
//将元素和set1合并生成一个新的set,原有set不变
val set2 = set1 + 4
//set中元素不能重复
val set3 = set1 ++ Set(5, 6, 7)
val set4 = Set(1,2,3,4) ++ set1
println(set4.getClass)
val set05 = set4.-(1,2)
val set06 = set4-1
val set5 = HashSet(3,4,5)
}
可变set集合
package cn.bigdata.collect
import scala.collection.mutable
object MutSetDemo extends App{
// Set可变集合的创建,如果import了可变集合,那么后续使用默认也是可变集合
import scala.collection.mutable.Set
val mutableSet = Set(1, 2, 3)
//创建一个可变的HashSet ()小括号里面不能有值
val set1 = new mutable.HashSet[Int]()
//向HashSet中添加元素
set1 += 2
//add等价于+=
set1.add(4)
set1 ++= Set(1,3,5)
println(set1)
//删除一个元素
set1 -= 5
set1.remove(2)
println(set1)
}
for(x <- mutableSet) {
println(x)
}
mutableSet.foreach(println(_))
mutableSet.foreach(x=>println("mutableSet = "+x))
(四)Scala Map
Map是一个对偶,映射的k-v键值对的集合,在一个map中可以包含若干组k-v映射关系,前提条件是k不能重复。同样map也有可变和不可变之分。
1、不可变Map
import scala.collection.immutable.Map
(1)、定义
因为Map是一个类似java中的接口,无法直接创建对象,所以需要使用它的伴生对象创建。
val map = Map[K, V]()
创建了一个key的类型为K,value的类型为V的map映射
(2)、初始化
不可变的map的数组,只能在初始化的时候指定
val map = Map[K, V](
(k1 -> v1),
(k2 -> v2),
。。。
)
或者
val map = Map[K, V](
k1 -> v1,
k2 -> v2,
。。。
)
(3)crud
//国家--->首都
val capital = Map[String, String](
("china" -> "BJ"),
"japan" -> "tokyo",
"south korea" -> "汉城"
)
//crud
//增 不可变的map不能追加
// capital += ("usa" -> "wc")
// capital("usa") = "wc"
//删除
val ret = capital.drop(1)//原来的集合不会发生变化
println("capital.drop(1)返回值:" + ret)
println("capital.drop(1)之后的map:" + capital)
//改
// capital("china") = "wc"
//查
/*
map.get(key)
返回值类Some或者None
也就是如果key在map中存在返回Some,意思就是存在
反之返回None
总结:value也就是可能有值,也可能没有值
scala中把这种可能有之,也可能没有值通过一个类Option[T]来表示
Option有两个子类:Some(T)和None
*/
println("capital.get(\"japan\"): " + capital.get("japan"))
println("capital.get(\"uk\"): " + capital.get("uk"))
//推荐使用,
val countryC = capital.getOrElse("china", "DBJ")
println(countryC)
//判断
if(capital.contains("uk")) {
println("capital(\"uk\"): " + capital("uk"))
}
//长度
capital.size
2.可变Map
import scala.collection.mutable.Map
(1)、定义并初始化
val map = mutable.Map[K, V]( (k1 -> v1), (k2 -> v2), 。。。 ) 创建了一个map,key的类型K,value的类型V,并且提供了初始化k-v键值对,当然可可以去掉其中的k-v,因为是可变的,那么便可以进行后续的元素追加。 |
(2)、crud
pc += ("广东" -> "粤")
pc += ("湖南" -> "湘")
pc += ("湖北" -> "鄂")
pc += ("广东" -> "粤")
pc("广西") = "桂"
println(pc)
//修改
pc("广西") = "gui"
//获取
println(pc.getOrElse("江西", "兰州"))
//删除
println(pc.remove("广东"))
println(pc)
3.Map通用操作
(1)Map大小
map.size |
(2)Map遍历
/**
* java版本
* for(Map.Entry<K, V> me : map.entrySet) {
* me.getKey
* me.getValue
* }
* for(K key : map.keySet()) {
* V v = map.get(key);
* }
*/
for((k, v) <- pc) {
println("key: " + k + ",value: " + v)
}
for(kv <- pc) {
println(kv)
}
println("------foreach遍历---------")
//函数式编程的foreach遍历
pc.foreach(kv => {
println(kv)
})
(五)Map
前面已经做了Map的介绍了,这里就做一个扩展。
1、综合案例:
文件:E:/data/hello.txt,内容为
hello you
hello you
hello me
hello you
hello you
hello me
This page outlines the steps for getting a Storm cluster up and running
处理方式一:
object _03MapOps {
def main(args: Array[String]): Unit = {
//scala中如何读取文件中的内容
val lines = Source.fromFile("E:/data/hello.txt").getLines()
//统计每个单词出现的次数
val map = mutable.HashMap[String, Int]()
for (line <- lines) {
val words = line.split("\\s+")
for(word <- words) {
// val countOption = map.get(word)
// if(countOption.isDefined) {
// val count = countOption.get + 1
// map.put(word, count)
// } else {
// map.put(word, 1)
// }
map.put(word, map.getOrElse(word, 0) + 1)
}
}
for((key, count) <- map) {
println(key + "--->" + count)
}
}
}
处理方式二:
def main(args: Array[String]): Unit = {
//scala中如何读取文件中的内容
val lines = Source.fromFile("E:/data/hello.txt").getLines()
val words = lines.flatMap(line => line.split("\\s+"))
val groupBy = words.toArray.groupBy(word => word)
val wordcount = groupBy.map(kv => (kv._1, kv._2.length))
wordcount.foreach(println)
println("------------------------------")
//注意,因为lines是Iterator,所以到这一步Iterator中已经没有值了,执行下面的代码时,需要将上面的代码注释掉
val ret = lines.toArray
.flatMap(_.split("\\s+"))
.groupBy(word => word)
.map(kv => (kv._1, kv._2.length))
ret.foreach(println)
}
2、扩展练习
对上述结果按照次数进行降序排序。
object Test {
def main(args: Array[String]): Unit = {
val lines = Source.fromFile("E:/data/hello.txt").getLines()
val ret = lines.toArray
.flatMap(_.split("\\s+"))
.groupBy(word => word)
.map(kv => (kv._1, kv._2.length))
ret.toList.sortWith((kv1, kv2) => kv1._2 > kv2._2).foreach(println)
}
}
二、模式匹配
1、模式匹配是Scala中非常强大的一种功能。模式匹配,其实类似于Java中的switch case语法,即对一个值进行条件判断,然后针对不同的输入条件,进行结果处理。
2、Scala的模式匹配的功能比Java的switch case语法的功能要强大地多,Java的switch case语法只能对值进行匹配。但是Scala的模式匹配除了可以对值进行匹配之外,还可以对类型进行匹配、对Array和List的元素情况进行匹配、对case class进行匹配、甚至对有值或没值(Option)进行匹配。
模式匹配常见的语法结构如下:
变量 match {
case 可能性1 => 操作1
。。。
case 可能性N => 操作N
case _ => 默认/其它操作
}
(一)可以用到Switch语句中
Scala强大的模式匹配机制,可以应用在switch语句、类型检查以及“析构”等场合。
def caseOps1(): Unit = {
println("please enter a character: ")
val ch = StdIn.readChar()
var sign = -1
ch match {
case '+' => sign = 1
case '-' => sign = 2
case '*' => sign = 3
case '/' => sign = 4
case _ => sign = -1
}
println("sign: " + sign)
}
//调用方法
caseOps1()
(二)守卫
//模式匹配当中,我们也可以通过条件进行判断
def main(args: Array[String]): Unit = {
var ch = "500"
var sign = 0
ch match {
case "+" => sign = 1
case "-" => sign = 2
case _ if ch.equals("500") => sign = 3
case _ => sign = 4
}
println(ch + " " + sign)
}
(三)、模式中的变量和类型模式
模式匹配中的变量,需要从语法结构说起:
变量 match {
case 可能性1 => 操作1
。。。
case 可能性N => 操作N
case _ => 默认/其它操作
}
启动如果是匹配的类型,可以将可能性x书写为==> 变量:类型,可能性也就成为=>变量1:类型1,这样做的好处是什么呢?就是直接进行类型转换,可以方便操作其特有的api。
如果类型是确定的,可以将:后面的类型省略掉。
1、变量
def caseOps3: Unit = { "Hello world~".foreach(c => println( c match { case ' ' => "space" case ch: Char => "Char: " + ch } )) } |
2、类型
def typeOps: Unit = {
class Person(name:String, age:Int) {
}
class Worker(name:String, age:Int) extends Person(name, age) {
def work(): Unit = {
println(s"工人同志${name}, 年龄为${age}正在热火朝天的休息~")
}
}
class Student(name:String, age:Int) extends Person(name, age) {
def study(): Unit = {
println(s"学生${name}, 年龄为${age}正在紧锣密鼓的玩游戏~")
}
}
def doSth(p:Person): Unit = {
p match {
//java中的Worker w = (Worker)p;
case w: Worker => w.work()
case s: Student => s.study()
case _ => println("类型不匹配")
}
}
doSth(new Worker("jack", 35))
}
(四)、匹配数组、列表和元组
1、对数组的匹配
val arr = Array(1, 3, 5) |
2、对列表的匹配
val lst = List(3, -1)
lst match {
case 0 :: Nil => println("only 0")
case _ ::Nil => println("List 只有一个元素的List,这个元素是任意值")
case x :: y :: Nil => println(s"x: $x y: $y")
case 0 :: tail => println("0 ...")
case _ => println("something else")
}
3、对元组的匹配
val tup = (2, 3, 7) |
(五)、样例类
所谓样例类,就是case class,是scala中一种专门用来携带数据的class,类似于java中的javabean,一般的定义方式需要定义该case class的主构造器,其所有的字段都在主构造器中完成声明,case class自动会提供所谓getter和setter方法。
case class的主构造函数接收的参数通常不需要使用var或val修饰,Scala自动就会使用val修饰(但是如果你自己使用var修饰,那么还是会按照var来)
Scala自动为case class定义了伴生对象,也就是object,并且定义了apply()方法,该方法接收主构造函数中相同的参数,并返回case class对象。
package cn.bigdata.cases
import scala.util.Random
case class SubmitTask(id: String, name: String)
case class HeartBeat(time: Long)
case object CheckTimeOutTask
object CaseDemo04 extends App{
val arr = Array(CheckTimeOutTask, HeartBeat(12333), SubmitTask("0001", "task-0001"))
arr(Random.nextInt(arr.length)) match {
case SubmitTask(id, name) => {
println(s"$id, $name")//前面需要加上s, $id直接取id的值
}
case HeartBeat(time) => {
println(time)
}
case CheckTimeOutTask => {
println("check")
}
}
}
总结:
- Scala编译器在对case class进行编译的时候做了特殊处理,扩展了其方法和功能,加入了scala.Product,scala.Serializable的特性。
- 为其提供了该类的伴生对象,且在伴生对象中提供了用于获取该类实例的apply方法。
- 对于case class 构造器参数,可以使用val,var修饰,也可以不使用,都是样例类的属性。其默认以public修饰,可允许外部调用。
- 样例类一般用作模式匹配,以及数据的封装和数据的传递,模拟Java中的bean。
(六)、Option
Option是Scala中有值(Some)或者没有值(None)的类型,Scala也可以对应Option进行模式匹配进行处理。
val map = Map[String, String](
"China" -> "BJ",
"India" -> "XDL",
"Japan" -> "Tokyo"
)
map.get("India") match {
case Some(capital) => println("India's capital is " + capital)
case None => println("所查国家不存在")
}
(七)、偏函数
被包在花括号内没有match的一组case语句是一个偏函数,它是PartialFunction[A, B]的一个实例,A代表参数类型,B代表返回类型,常用作输入模式匹配
object PartialFuncDemo {
def func1: PartialFunction[String, Int] = {
case "one" => 1
case "two" => 2
case _ => -1
}
def func2(num: String) : Int = num match {
case "one" => 1
case "two" => 2
case _ => -1
}
def main(args: Array[String]) {
println(func1("one"))
println(func2("one"))
}
}