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)
    }
}

你可能感兴趣的:(Scala学习笔记 A2/L1篇 - 模式匹配和样例类 Pattern Matching and Case Classes)