Scala学习笔记 A2/L1篇 - 模式匹配和样例类 Pattern Matching and Case Classes

教材:快学Scala

chapter 14. 模式匹配和样例类 Pattern Matching and Case Classes

  • The match expression is a better switch, without fall-through.

14.1 A Better Switch

// match is an expression
sign = ch match {
    case '+' => 1
    case '-' => -1
    case _ => 0 // default 
}

// guards
ch match {
    ...
    case ch if Character.isDigit(ch) => digit = Character.digit(ch, 10) 
}

// type patterns 不需要isInstanceOf/asInstanceOf
obj match {
    case x: Int => x 
    case s: String => Integer.parseInt(s)
    case _: BigInt => Int.MaxValue // BigInt类型的对象
    case BigInt => -1 // Class类型的BigInt对象
    // 匹配发生在运行期,JVM中的泛型类型信息是被擦掉的,所以不能用Map[Int, Int]
    case m: Map[_, _] => m
    case _ => 0
}

// array
// 原理:Array.unapplySeq(arr)产出一个序列的值
arr match {
    case Array(0) => "0"
    case Array(x, y) => x + " " + y
    case Array(0, _*) => "0 ..."
    case _ => "..."
}

// list
lst match {
    case 0 :: Nil => "0"
    case x :: y :: Nil => x + " " + y
    case 0 :: tail => "0 ..."
    case _ => "..."
}

// tuple
pair match {
    case (0, _) => "0 ..."
    case (x, 0) => x + " 0"
    case _ => "..."
}

// re
// 原理:pattern.unapplySeq("99 bottles")
val pattern = "([0-9]+) ([a-z]+)".r
"99 bottles" match {
    case pattern(num, item) => println(num, item) // (99,bottles)
}

14.8 for表达式中的模式

在for推导式for (... <- ...)中使用带变量的模式
for ((k, v) <- System.getProperties() if v == "") ...
失败的匹配将被安静地忽略
for ((k, "") <- System.getProperties()) ...

14.9 Case Classes

abstract class Amount // 将一类case class定义到同一个抽象类中方便匹配
case class Dollor(value: Double) extends Amount
case class Currency(value: Double, unit: String) extends Amount
case object Nothing extends Amount // 样例对象 不带()

amt match {
    case Dollor(v) => "$" + v
    case Currency(_, u) => "Oh noes, I got " + u
    case Nothing => ""
}
  • case class的几个特性
    构造器中的每个参数都为val
    提供apply方法构造实例,不用new
    提供unapply方法用于模式匹配
    自动生成toString equals hashCode copy方法
val amt = Currency(30, "EUR")
val amt2 = amt.copy(unit = "CHN") // 可以用带名参数copy的同时修改某些属性值
  • case语句的中置表示法 Infix Notation in case Clauses
    条件:unapply方法返回一个pair结果都可以用中置表示法
    amt match { case a Currency u => ... }
    等价于
    amt match { case Currency(a, u) => ... }
  • 例子:List的实现
abstract class List
case object Nil extends List
case class ::[E](head: E, tail: List[E]) extends List[E]

lst match { case h :: t => ... }
等同于
lst match { case ::(h, t) => ... } 将调用::.unapply(lst)

  • 匹配嵌套结构
abstract class Item
case class Article(desc: String, price: Double) extends Item
case class Bundle(desc: String, discount: Double, items: Item*) extends Item
// Item* 表示后面有>=0个Item参数

// 构造实例
val item = Bundle("Father's day special", 20.0, 
    Article("Scala for impatient", 39.95),
    Bundle("Anchor Distillery Sampler", 10.0,
        Article("Old Potrero", 79.95),
        Article("Junipero", 32)
    ),
    Multiple(2, Bundle("xxx", 10, Article("yyy", 23))), // 26
    Multiple(2, Article("yyy", 47)), // 94
    Multiple(5, Multiple(4, Article("zzz", 1))) // 20
)

item match {
    case Bundle(_, _, Article(desc, _), _*) => println(desc) // 匹配第一个article的描述
    case Bundle(_, _, art @ Article(_, _), rest @ _*) => println(art.desc) // 同上,用@绑定到变量
    case Bundle(_, _, Bundle(_, _, art @ Article(_, _), rest @ _*), rest2 @ _*) => println(art.desc)
}

def price(it: Item): Double = it match {
    case Article(_, p) => p
    case Bundle(_, discount, it @ _*) => it.map(price(_)).sum - discount
}

优点:代码更精简;不需要new;有免费的toString equals hashCode copy
缺点:若需要增加一种新的Item,对所有的match语句都要修改,一点都不OOP(enrage OO purists)
price函数在这里应该定义为每个Item子类各自实现的函数更合适
所以case class更适用于makeup不会改变的结构,即确保已经列出了所有可能的case class的选择

  • 相同参数的case class实例它们是等效的,因此case class也叫value class(值类)
val c1 = Currency(10, "EUR")
val c2 = Currency(10, "EUR")
c1 == c2 // res81: Boolean = true

14.14 密封类 Sealed Classes

  • 目的:用case class做模式匹配时,想让编译器帮你确保你已列出了所有可能的选择
sealed abstract class Amount
case class Dollor(value: Double) extends Amount
case class Currency(value: Double, unit: String) extends Amount
  • 效果:密封类的所有子类都必须在与该密封类相同的文件中定义
  • 最佳实践:让同一组样例类都扩展自某个密封的类或者trait

14.16 Option Type

  • Option:用样例类表示那种可能存在(Some样例类),也可能不存在(None样例对象)的值
  • Map的get方法返回一个Option,还有getOrElse方法
  • 用for推导式自动忽略None值
    for (score <- scores.get("Alice")) println(score)
    scores.get("Alice").foreach(println(_))

练习答案

// source: C:\Program Files\Java\jdk1.8.0_101\src.zip
"case [^:\n]+:".r // 10540 matches across 680 files
"break;[ \t\n]+case [^:\n]+:".r // 3547 matches across 397 files
"break;[ \t\n]+default:".r // 469 matches across 229 files

val res = 4016.0 / 10540 // res: Double = 0.3810
  1. def swap(pair: (Int, Int)) = pair match { case p: (Int, Int) => (p._2, p._1) }
def swap(s: Array[Int]) = s match {
    case Array(x, y, _*) => s(0) = y; s(1) = x; s
    case _ => s
}
sealed abstract class Item
case class Article(desc: String, price: Double) extends Item
case class Bundle(desc: String, discount: Double, items: Item*) extends Item
case class Multiple(amount: Int, item: Item) extends Item
def price(it: Item): Double = it match {
    case Article(_, p) => p
    case Bundle(_, discount, it @ _*) => it.map(price(_)).sum - discount
    case Multiple(a, item) => a * price(item)
}

val item = Bundle("Father's day special", 20.0, 
    Article("Scala for impatient", 39.95),
    Bundle("Anchor Distillery Sampler", 10.0,
        Article("Old Potrero", 79.95),
        Article("Junipero", 32)
    ),
    Multiple(2, Bundle("xxx", 10, Article("yyy", 23))), // 26
    Multiple(2, Article("yyy", 47)), // 94
    Multiple(5, Multiple(4, Article("zzz", 1))) // 20
)

price(item) // res96: Double = 261.9
def leafSum(root: List[Any]): Int = root map { node: Any =>
    node match {
        case x: Int => x
        case t: List[_] => leafSum(t)
    }
} reduceLeft(_ + _)
sealed abstract class BinaryTree
case class Leaf(value: Int) extends BinaryTree
case class Node(left: BinaryTree, right: BinaryTree) extends BinaryTree
def leafSum1(root: BinaryTree): Int = root match {
    case Leaf(v) => v
    case Node(l, r) => leafSum(l) + leafSum(r)
}
sealed abstract class Tree extends BinaryTree
case class Leaf(value: Int) extends Tree
case class Node(children: Tree*) extends Tree
def leafSum2(root: Tree): Int = root match {
    case Leaf(v) => v
    case Node(ch @ _*) => 
        var sum = 0
        for (c <- ch) sum += leafSum(c)
        sum
}
sealed abstract class EvalTree extends Tree
case class Leaf(value: Int) extends EvalTree
case class Node(op: Char, children: EvalTree*) extends EvalTree
def eval(root: EvalTree): Int = root match {
    case Leaf(v) => v
    case Node(op, ch @ _*) => 
        var res = scala.collection.mutable.ArrayBuffer[Int]()
        for (c <- ch) res += eval(c)
        op match {
            case '+' => res.foldLeft(0)(_ + _)
            case '*' => res.foldLeft(1)(_ * _)
            case '-' if (res.length > 1) => res.reduceLeft(_ - _)
            case '-' if (res.length == 1) => -res(0)
            case _ => 0
        }
}
eval(Node('+', Node('*', Leaf(3), Leaf(8)), Leaf(2), Node('-', Leaf(5))))
def lstSum1(lst: List[Option[Int]]): Int = {
    var sum: Int = 0
    lst.map(e => e.foreach(sum += _))
    sum
}

def lstSum2(lst: List[Option[Int]]): Int = {
    var sum: Int = 0
    for (Some(e) <- lst) sum += e
    sum
}
def compose(f: (Double) => Option[Double], g: (Double) => Option[Double]) = (x: Double) => {
    g(x) match {
        case None => None
        case Some(y) => f(y)
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容