美文网首页
Scala学习笔记 A2/L1篇 - 模式匹配和样例类 Patt

Scala学习笔记 A2/L1篇 - 模式匹配和样例类 Patt

作者: hakase_nano | 来源:发表于2018-08-21 14:57 被阅读0次

    教材:快学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篇 - 模式匹配和样例类 Patt

          本文链接:https://www.haomeiwen.com/subject/mhndiftx.html