Spark样本类与模式匹配
一、前言
样本类(case class)与模式匹配(pattern matching)是Scala中一个比较复杂的概念,往往让人感觉深陷泥沼。我在这里对Scala中的样本类与模式匹配进行了一些整理,希望让大家有些收获。
要学习样本类与模式匹配,先要理解这两个基本概念 。样本类是Scala提出的新概念,简单可以理解成希望用来做模式匹配的类加上case关键词就是样本类。模式匹配可以拆开来理解。这里模式不同于设计模式的模式,而是数据结构上的模式。这里的模式(pattern)是一个只包含变量、 有点类似于Java正则表达式中的模式,不过正则中模式只是用于匹配字符串而已,而这里的模式是针对某种类型或数据结构进行抽象出的表达式。
Scala的模式匹配机制,可以应用在switch语句、类型检查以及”析构“等场合。样本类是对模式匹配进行了优化。
二、样本类
样本类与普通类的区别除了在前面添加case关键词之外,Scala还自动给样本类添加了一些方法。下面通过例子进行讲解。
1
2
3
4
5
|
abstract class Expr case class FibonacciExpr(n : Int) extends Expr { // 样本类 require(n > = 0 ) } |
首先,样本类会添加与类名一致的工厂方法,可以使用该工厂方法创建对象
1
2
|
scala> val fb = FibonacciExpr( 12 ) fb : FibonacciExpr = FibonacciExpr( 12 ) |
其次,样本类参数类表中的参数获得val前缀,所以它被当作字段维护
1
2
|
scala> fb.n res 0 : Int = 12 |
最后,编译器为样本类添加了hashcode,equals和toString等方法的“自然”实现
1
2
|
scala> fb.toString() res 2 : String = FibonacciExpr( 12 ) |
三、模式匹配
更好的switch
1
2
3
4
5
|
ch match { case '+' = > sign = 1 case '-' = > sign = - 1 case _ = > sign = 0 } |
上面的代码中为match表达式,"case _"对应于Java的default情况,match对应与Java中switch,不过位置与Java不同
Scala: 选择器 match {备选项} Java: switch (选择器) {备选器}
一个模式匹配中包含一系类的备选项,而每个备选项开始于case关键词 ,并且都包含一个模式(pattern)及一到多个表达式。Java风格的switch能够自然地表达为match表达式, 但两者还是有一定区别。首先,match是Scala的表达式,不是语句,即它始终以值作为返回结果。其次,Scala的备选项表达式永远进行下一个case,无需break。第三、如果没有模式匹配,会跑出MatchError异常,所以必须考虑到所有的情况。
模式的种类
我们已经知道模式是一种针对每种类型或数据结构抽象出表达式,但是模式有多种类型。
通配模式与常量模式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
def describe(x : Any) = x match { case 5 = > "five" case true = > "truth" case "hello" = > "hi!" case Nil = > "the empyt list" case _ = > "somethint else" } // 运行结果 scala> describe( 5 ) res 3 : String = five scala> describe( true ) res 4 : String = truth scala> describe( "hello" ) res 5 : String = hi! scala> describe( 3.23 ) res 6 : String = somethint else |
常量模式仅匹配自身 。任何字面量都可以作为常量,例如:5,true,”hello“都是常量模式。任何val变量或单例对象也可作为常量。而通配模式"_"可以匹配任意对象,经常作为最后的匹配或用来忽略对象的类型。
变量模式
单纯的变量模式没有匹配判断的过程,只是为将传入的对象重命名新的变量名 然后在匹配完成后中表达式中使用该传入的对象
1
|
scala> site match { case whateverName = > println(whateverName) } |
上面的例子中匹配的site对象用whateverName进行指代,但是变量名有命名规则: 用小写字母开始的名称为变量,否则认为是常量 。
然而变量模式不会单独使用,而是在多种模式中组合使用,如下所示
1
|
List( 1 , 2 ) match { case List(x, 2 ) = > println(x) } |
上面的例子中x就是将匹配到的第一个元素用变量x标记
构造器模式
构造器模式是真正能体现模式匹配强大的地方!构造器模式能够支持深度匹配(deep match),这是它强大的原因,匹配模式不仅匹配外层的构造器,而且匹配构造器参数是否匹配,这样的话,可以检查到构造器对象内部的任意深度 。实例如下
1
2
3
4
5
6
7
|
scala> : paste //抽象节点 trait Node //具体的节点实现,有两个子节点 case class TreeNode(v : String, left : Node, right : Node) extends Node //Tree,构造参数是根节点 case class Tree(root : TreeNode) |
构造一个根节点包含2个子节点的树
1
|
scala> val tree = Tree(TreeNode( "root" ,TreeNode( "left" , null , null ),TreeNode( "right" , null , null ))) |
如果我们期望一个树的构成是根节点的左子节点值为”left”,右子节点值为”right”并且右子节点没有子节点
那么可以用下面的方式匹配
1
2
3
4
|
scala> tree.root match { case TreeNode( _ , TreeNode( "left" , _ , _ ), TreeNode( "right" , null , null )) = > println( "bingo" ) } |
在上面的匹配中,模式检查顶层对象TreeNode,以及第二次对象TreeNode("left",_,_), TreeNode("right",null,null),_,和最后第三层对象的参数值”left“,"right","_",这个模式仅有一行但却能检查三层深度。
类型模式
类型模式,就是判断对象的类型,相较于isInstanceOf而言类型模式更加优秀。
1
2
3
4
5
6
|
obj match { case x : Int = > x case s : String = > Integer.parseInt(s) case _: BigInt = > Int.MaxValue case _ = > 0 } |
类型匹配是发生在运行期的,因此JVM中泛型的类型信息会被擦除,所以不能匹配泛型的特定类型
1
2
|
case m : Map[String, Int] = > ... // 不行 case m : Map[ _ , _ ] = > ... // 匹配通用的Map,OK |
但对于数组而言,因为在Java与Scala中数组被特殊处理了,数组的元素类型与数组值保存在一起,因此它可以做模式匹配
1
|
case a : Array[String] = > "yes" |
抽取器模式(extractor pattern)
抽取器模式使用unapply来提取固定数量的对象,使用unapplySeq来提取一个序列。先看一个例子
1
2
3
4
5
6
|
arr match { case Array( 0 ) = > "0" // 匹配包含0的数组 case Array(x, y) = > x + " " + y // 匹配任何带有两个元素的数组,并将元素绑定到x和y case Array( 0 , _ *) = > "0 ..." // 匹配任何以0开始的数组 case _ = > "something else" } |
在前面的代码 case Array(0, x) => ...中, Array(0, x)部分实际上是使用了伴生对象中的提取器,实际调用形式是: Array.unapplySeq(arr)。根据Javadoc,提取器方法接受一个Array参数,返回一个Option。
使用抽取器模式能够数组、列表和元组,还可以使用在正则表达式
1
2
3
4
|
val pattern = "([0-9]+) ([a-z]+)" .r "99 bottles" match { case pattern(num, item) = > ... } |
变量绑定模式
变量绑定模式可以对其它模式添加变量,代码实例如下:
1
2
3
|
scala> tree.root match { case TreeNode( _ , leftNode @ TreeNode( "left" , _ , _ ), _ ) = > leftNode } |
上面代码中,leftNode变量 使用@符号绑定到匹配的左节点上,正如代码中写的,匹配成功后返回左节点
四、模式守卫(pattern guard)
模式守卫能够重新制定匹配规则, 代码实例如下:
1
2
3
4
5
6
7
|
abstract class Expr case class Number(num : Double) extends Expr case class BinOp(operator : String,left : Expr, right : Expr) extends Expr def simplifyTop(expr : Expr) : Expr = expr match { case BinOp( "+" ,x,y) if x == y = > BinOp( "*" ,x,Number( 2 )) } |
上面的代码中,对模式进行了简化,对表达式(x+x)转换成(x*2)。
五、封闭类
当使用样本类做模式匹配时,必须确信考虑到所有可能的情况,有时候可以通过添加默认处理做到这点,这里还有一种方法,那就是封闭类。
将样本类的超类声明为sealed,则该类为封闭类,则除了封闭类所在的文件之外不能添加任何新的子类,即子类与密封类在一个文件中定义。定义为封闭类之后,编译器能够确保已经列出所有可能的选择。
1
2
3
4
5
|
sealed abstract class Expr case class Var(name : String) extends Expr case class Number(num : Double) extends Expr case class UnOp(operator : String, arg : Expr) extends Expr case class BinOp(operator : String,left : Expr, right : Expr) extends Expr |
六、Option类型
Option类型用来表示可能存在也可能不存在的值,用于取代null值得使用。
Option有两种形式:Some(x),x是实际值,None表示没有值
1
2
3
4
|
scores.get( "Alice" ) match { case Some(score) = > println(score) case None = > println( "No score" ) } |
七、偏函数(Partial function)
偏函数到底是什么呢? 概括来说,偏函数是继承特质 PartialFunction的一个一元函数,它只在部分输入上有定义, 并且允许使用者去检查其在一个给定的输入上是否有定义。为此,特质 PartialFunction 提供了一个 isDefinedAt 方法。 事实上,类型 PartialFunction[-A, +B] 扩展了类型 (A) => B (一元函数,也可以写成 Function1[A, B] )。 模式匹配型的匿名函数的类型就是 PartialFunction 。
偏函数有两种定义方法
1
2
3
4
5
6
7
|
def p 1 : PartialFunction[Int, Int] = { case x if x > 1 = > 1 } def p 2 = (x : Int) = > x match { case x if x > 1 = > 1 } |
Spark样本类与模式匹配的更多相关文章
- scala学习笔记(四)样本类与模式匹配
访问修饰符 格式:private[x]或protected[x],x指某个所属包.类或单例对象,表示被修饰的类(或方法.单例对象),在X域中公开,在x域范围内都可以访问: private[包名]:在该 ...
- Scala学习文档-样本类与模式匹配(match,case,Option)
样本类:添加了case的类便是样本类.这种修饰符可以让Scala编译器自动为这个类添加一些语法上的便捷设定. //样本类case class //层级包括一个抽象基类Expr和四个子类,每个代表一种表 ...
- Scala学习笔记——样本类和模式匹配
1.样本类 在申明的类前面加上一个case修饰符,带有这种修饰符的类被称为样本类(case class). 被申明为样本类的类的特点:1.会添加和类名一致的工厂方法:2.样本类参数列表中的所有参数隐式 ...
- scala模式匹配及样本类
样本类 1.带有case关键字的类被称为样本类: 例如:abstract class Expr case class Var(name: String) extends Expr case class ...
- org.apache.spark.logging类报错
一,1 在使用spark读取kafka数据时,当spark升级到2.0之后,出现如上问题:之前遇到了,当时在工程里面添加了org.apache.spark.Logging类,能够运行. 但是在后期使用 ...
- Spark核心类:弹性分布式数据集RDD及其转换和操作pyspark.RDD
http://blog.csdn.net/pipisorry/article/details/53257188 弹性分布式数据集RDD(Resilient Distributed Dataset) 术 ...
- Spark RDD类源码阅读
每天进步一点点~开搞~ abstract class RDD[T: ClassTag]( //@transient 注解表示将字段标记为瞬态的 @transient private var _sc: ...
- Spark核心类:SQLContext和DataFrame
http://blog.csdn.net/pipisorry/article/details/53320669 pyspark.sql.SQLContext Main entry point for ...
- spark reduce类操作
reduce类函数分析: ---------------------------------------------------------------------------- 待补全 ------ ...
随机推荐
- SET NAMES
High Performance MySQL, Third Editionby Baron Schwartz, Peter Zaitsev, and Vadim Tkachenko Settings ...
- mysql主从服务器的配置
使用mysql主从复制的好处有: 1.采用主从服务器这种架构,稳定性得以提升.如果主服务器发生故障,我们可以使用从服务器来提供服务. 2.在主从服务器上分开处理用户的请求,可以提升数据处理效率. 3. ...
- 【服务器】如何在服务器发布网站?Sasa讲解
一.网站发布过程 1.可以在淘宝.万维网上买服务器,然后客服提供一个服务器的远程ip链接,我们通过我们电脑的远程去链接这个远程服务器就可以对这个服务器进行控制了.. 2.将待发布网站的程序在本地保存, ...
- 【Python】web.py初识学习
简单而直接的Python web 框架:web.py 2016年11月03日 14:09:08 擒贼先擒王 阅读数:35157更多 个人分类: Web From:https://www.oschi ...
- python3内置函数大全(顺序排列)
python3内置函数大全 内置函数 (1)abs(), 绝对值或复数的模 1 print(abs(-6))#>>>>6 (2)all() 接受一个迭代器,如果迭代器的所有 ...
- 十四、springboot全局处理异常(@ControllerAdvice + @ExceptionHandler)
1.@ControllerAdvice 1.场景一 在构建RestFul的今天,我们一般会限定好返回数据的格式比如: { "code": 0, "data": ...
- PAT Sign In and Sign Out[非常简单]
1006 Sign In and Sign Out (25)(25 分) At the beginning of every day, the first person who signs in th ...
- 浏览器 extension和plugin的区别[来自知乎]
"扩展"和"插件",其实都是软件组件的一种形式,Chrome 只不过是把两种类型的组件分别给与了专有名称,一个叫"扩展",另一个叫" ...
- [LeetCode] 257. Binary Tree Paths_ Easy tag: DFS
Given a binary tree, return all root-to-leaf paths. Note: A leaf is a node with no children. Example ...
- matplotlib —— 调整坐标轴
import matplotlib.pyplot as plt import numpy as np # 绘制普通图像 x = np.linspace(-1, 1, 50) y1 = 2 * x + ...