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對象
    // 匹配發(fā)生在運行期,JVM中的泛型類型信息是被擦掉的,所以不能用Map[Int, Int]
    case m: Map[_, _] => m
    case _ => 0
}

// array
// 原理:Array.unapplySeq(arr)產(chǎn)出一個序列的值
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的幾個特性
    構造器中的每個參數(shù)都為val
    提供apply方法構造實例,不用new
    提供unapply方法用于模式匹配
    自動生成toString equals hashCode copy方法
val amt = Currency(30, "EUR")
val amt2 = amt.copy(unit = "CHN") // 可以用帶名參數(shù)copy的同時修改某些屬性值
  • case語句的中置表示法 Infix Notation in case Clauses
    條件:unapply方法返回一個pair結果都可以用中置表示法
    amt match { case a Currency u => ... }
    等價于
    amt match { case Currency(a, u) => ... }
  • 例子:List的實現(xiàn)
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參數(shù)

// 構造實例
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
}

優(yōu)點:代碼更精簡;不需要new;有免費的toString equals hashCode copy
缺點:若需要增加一種新的Item,對所有的match語句都要修改,一點都不OOP(enrage OO purists)
price函數(shù)在這里應該定義為每個Item子類各自實現(xiàn)的函數(shù)更合適
所以case class更適用于makeup不會改變的結構,即確保已經(jīng)列出了所有可能的case class的選擇

  • 相同參數(shù)的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)
    }
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容