zoukankan      html  css  js  c++  java
  • Scala核心编程_第13章 模式匹配

    基本介绍

    Scala中的模式匹配类似于Java中的switch语法,但是更加强大。

    模式匹配语法中,采用match关键字声明,每个分支采用case关键字进行声明,当需要匹配时,会从第一个case分支开始,如果匹配成功,那么执行对应的逻辑代码,如果匹配不成功,继续执行下一个分支进行判断。

    如果所有case都不匹配,那么会执行case _ 分支,类似于Java中default语句。

    Java中的Switch

    // Java
    int i = 1;
    switch ( i ) {
        case 0 :
                break; 
        case 1 : 
                break;
        default : 
                break
    }

    scala中的模式匹配

        val operate = '#'
        val n1 = 20
        val n2 = 10
        var res = 0
        operate match {
          case '+' => res = n1 + n2
          case '-' => res = n1 - n2
          case '*' => res = n1 * n2
          case '/' => res = n1 / n2
          case _ => println("oper error")
        }
        println("res=" + res)
      }
    

    1. 命中case,不用break语句,自动中断case,不再去匹配下面的case。可以在match中使用多种类型,=> 后面的代码块到下一个 case, 是作为一个整体执行,可选用{} 扩起来。
    2. 如果所有case都不匹配,那么会执行case _ 分支,类似于Java中default语句
    3. 如果所有case都不匹配,又没有写case _ 分支,那么会抛出MatchError

    守卫

    基本介绍
    如果想要表达匹配某个范围的数据,就需要在模式匹配中增加条件守卫

        for (word <- "+-3!") {
          var sign = 0
          var digit = 0
          word match {
            case '+' => sign = 1
            case '-' => sign = -1
            // 说明..
            case _ if word.toString.equals("3") => digit = 3
            case _ => sign = 2
          }
          println("word:"+word + " " + sign + " " + digit)
        }
    

     无论是case_分支还是正常的分支都可以有守卫,守卫的含义是条件不仅要满足case后面的也要满足守卫里面的条件,否则会往下面的case进行匹配。

        for (word <- "+-3!") {
          var sign = 0
          var digit = 0
          word match {
            case '+' => sign = 1
            case '-' if sign!=0 => sign = -1
            case '-' if sign==0 => sign = -10; println("---")
            // 说明..
            case _ if word.toString.equals("3") => digit = 3
            case _ => sign = 2
          }
          println("word:"+word + " " + sign + " " + digit)
        }
    

    模式中的变量

    基本介绍

    如果在case关键字后跟变量名,那么match前表达式的值会赋给那个变量。

        for (word <- "+-3!") {
          var sign = 0
          var digit = 0
          word match {
            case '+' => sign = 1
            case '-' if sign!=0 =>sign = -1
            case getVar if word=='3' => println("getVar here ===>:"+getVar)
            // 说明..
            case _ if word.toString.equals("3") => digit = 3
            case _ => sign = 2
          }
          println("word:"+word + " " + sign + " " + digit)
        }
    

      

    类型匹配

    基本介绍

    可以匹配对象的任意类型,这样做避免了使用isInstanceOf和asInstanceOf方法

    应用案例

        while(true){
          val Inputcontent = Console.readInt()
          val arr= Array[Any](1,Map(1->"wqbin"),Array(1,23),1.1)
          val obj=arr(Inputcontent)
          val result = obj match {
            case a : Int => a
            case b : Map[String, Int] => "对象是一个字符串-数字的Map集合"
            case c : Map[Int, String] => "对象是一个数字-字符串的Map集合"
            case e : Map[Any, Any]  => "对象是一个的Map集合"
            case f : Array[String] => "对象是一个字符串数组"
            case g : Array[Int] => "对象是一个数字数组"
            case h : BigInt => Int.MaxValue
            case _ => "啥也不是"
          }
          println("result===>",result)
        }
    

    1. Map[String, Int] 和Map[Int, String]是两种不同的类型,其它类推。
    2. 在进行类型匹配时,编译器会预先检测是否有可能的匹配,如果没有则报错.
    3. val result = obj match {case i : Int => i},这里 case i : Int => i 表示 将 i = obj (其它类推),然后再判断类型
    4. 如果 case _ 出现在match 中间,则表示隐藏变量名,即不使用,而不是表示默认匹配。其实类似于加上了对数据类型的守卫判断。

    类型匹配与参数提取

    以数组为例

        for (arr <- Array(Array(0), Array(1, 0), Array(0, 1, 0),
          Array(1, 1, 0), Array(1, 1, 0, 1))) {
          val result = arr match {
            case Array(0) => "0"
            case Array(x, y) => "Array(x, y)==>" + x + "=" + y
            case Array(0, _*) => "以0开头和数组"
            case _ => "其他集合"
          }
          println("result = " + result)
        }
    1. Array(0) 匹配只有一个元素且为0的数组。
    2. Array(x,y) 匹配数组有两个元素,并将两个元素赋值为x和y。当然可以依次类推Array(x,y,z) 匹配数组有3个元素的等等....
    3. Array(0,_*) 匹配数组以0开始

    以列表为例

        for (list <- Array(List(0), List(1, 0), List(0, 0, 0), List(1, 1, 0), List(1, 0, 0))) {
          val result = list match {
            case 0 :: Nil => "[0]" //
            case x :: y :: Nil => "["+x + " " + y +"]"
            case 0 :: tail => "[0 ...]" //
            case head :: 0::Nil => "[ ...0]" //
            case _ => "something else"
          }
          println(result)
        }
    

      

     以元祖为例

        for (pair <- Array((0, 1), (1, 0), (1, 1),(1,0,2))) {
          val result = pair match { //
            case (0, _) => "(0 ...)" //
            case (y, 0) => "(... 0)" //
            case (x, y) => "("+x+","+y+")"
            case _ => "other" //.
          }
          println(result)
        }
    

    对象匹配

    基本介绍

    本质是,把match匹配的当参数传入在case过程中的对象的unapply方法(对象提取器)

    1. 返回Some集合则为匹配成功
    2. 返回none集合则为匹配失败
        object Square {
          var score: Double = _
          def unapply(z: Double): Option[Double] = {
            println("unapply被调用 z 是=" + z)
            Some(math.sqrt(z))
            //    None
          }

    说明

    1. unapply方法是对象提取器
    2. 接收z:Double 类型
    3. 返回类型是Option[Double]
    4. 返回的值是 Some(math.sqrt(z)) 返回z的开平方的值,并放入到Some(x)
        // 模式匹配使用:
        val number: Double = Square(6.0)// 36.0 //调用apply
    
        number match {
    
          case Square(n) => println("匹配成功 n=" + n)
          case _ => println("nothing matched")
        }

    说明 case Square(n) 的运行的机制

    1. 当匹配到 case Square(n)
    2. 调用Square 的 unapply(z: Double),z 的值就是 number
    3. 如果对象提取器 unapply(z: Double) 返回值调用 def isEmpty: Boolean,则False表示匹配成功,同时将6 赋给 Square(n) 的n
    4. 如果对象提取器 unapply(z: Double) 返回的是None ,则表示匹配不成功

    总结:

    1. 构建对象时apply会被调用 ,比如 val n1 = Square(5)
    2. 当将 Square(n) 写在 case 后时[case Square(n) => xxx],会默认调用unapply 方法(对象提取器)
    3. number 会被 传递给def unapply(z: Double) 的 z 形参
    4. 如果返回的是Some集合,则unapply提取器返回的结果会返回给 n 这个形参
    5. case中对象的unapply方法(提取器)返回some集合则为匹配成功
    6. 返回none集合则为匹配失败
        object Names {
          //当构造器是多个参数时,就会触发这个对象提取器
          def unapplySeq(str: String): Option[Seq[String]] = {
            if (str.contains(","))
              Some(str.split(","))
            else None
          }
        }
        val namesString = "Alice,Bob,Thomas" //字符串
        //说明
        namesString match {
          // 当 执行   case Names(first, second, third)
          // 1. 会调用 unapplySeq(str),把 "Alice,Bob,Thomas" 传入给 str
          // 2. 如果 返回的是 Some("Alice","Bob","Thomas"),分别给 (first, second, third)注意,这里的返回的值的个数需要和 (first, second, third)要一样
          // 3. 如果返回的None ,表示匹配失败
    
          case Names(first, _, second) =>
            println("the string contains three people's names:"+s"$first $second")
          case _ => println("nothing matched")
        }

      

     Option是啥? 

    说明:我感觉这篇博客写的非常好。

    变量声明中与for循环中的模式匹配

    变量声明中

    match中每一个case都可以单独提取出来,意思是一样的.

    应用案例

    val (x, y) = (1, 2)
    val (q, r) = BigInt(10) /% 3 //说明 q = BigInt(10) / 3 r = BigInt(10) % 3
    val arr = Array(1, 7, 2, 9)
    val Array(first, second, _*) = arr // 提出arr的前两个元素
    println(first, second)

    for循环中的

        val map = Map("A" -> 1, "B" -> 0, "C" -> 3)
        for ((k, v) <- map) {
          println(k + " -> " + v)
        }
        //说明
        for ((k, 0) <- map) {
          println(k + " --> " + 0)
        }
        //说明
        for ((k, v) <- map if v == 0) {
          println(k + " ---> " + v)
        }
    

     样例类

    case类在模式匹配和actor中经常使用到,当一个类被定义成为case类后,Scala会自动帮你创建一个伴生对象并帮你实现了一系列方法,如下:

    scala> case class GoodStudent(name: String, score: Int){}
    defined class GoodStudent
    

    他底层的代码逻辑如下:

    对object类型增加了一些方法。
    作用如下:
    1.实现了apply方法,意味着你不需要使用new关键字就能创建该类对象
    scala> var st=GoodStudent("wqbin",100)
    st: GoodStudent = GoodStudent(wqbin,100)
    

    2.实现了unapply方法,可以通过模式匹配来获取类属性,是Scala中抽取器的实现和模式匹配的关键方法。

    scala> st match{case GoodStudent(x,y)=>println(x,y)}
    (wqbin,100)
    

    3.实现了类构造参数的getter方法(构造参数默认被声明为val),但是当你构造参数是声明为var类型的,它将帮你实现setter和getter方法(不建议将构造参数声明为var)

    构造参数为val的情况(默认):
    scala> st.name
    res1: String = wqbin
    
    scala> st.name="wang"
    <console>:14: error: reassignment to val
           st.name="wang"
    

    构造参数为var的情况:

    scala> case class GoodStudent(var name: String, score: Int){}
    defined class GoodStudent
    
    scala> var st=GoodStudent("wqbin",100)
    st: GoodStudent = GoodStudent(wqbin,100)
    
    scala> st.name="wang"
    st.name: String = wang
    
    scala> st
    res2: GoodStudent = GoodStudent(wang,100)
    

    4.样例类的copy方法和带名参数

    copy创建一个与现有对象值相同的新对象,并可以通过带名参数来修改某些属性。

    scala> val st1=st.copy()
    st1: GoodStudent = GoodStudent(wang,100)
    
    scala> st1.hashCode()
    res3: Int = -605011778
    
    scala> st.hashCode()
    res4: Int = -605011778
    
    scala> val st2=st.copy("wqbin",120)
    st2: GoodStudent = GoodStudent(wqbin,120)
    
    scala> st2.hashCode()
    res5: Int = -1824578579
    

    好像命名我们进行了copy,st1与st的hashcode内存地址是不同的但是st1与st哈希码值是一样,是因为case样例类重写了hashcode方法。

     5.样例类的也重写了equals和toString方法

    scala> GoodStudent("wqbin",100) ==GoodStudent("wqbin",100)
    Boolean = true
    
    scala> st1==st2
    Boolean = false
    
    scala> st1==st
    Boolean = true
    
    scala> st.toString()
    String = GoodStudent(wang,100)
    

    中置表达式

    基本介绍

    什么是中置表达式?1 + 2,这就是一个中置表达式。如果unapply方法产出一个元组,你可以在case语句中使用中置表示法。比如可以匹配一个List序列

        List(1, 3, 5, 9) match { //修改并测试
          //1.两个元素间::叫中置表达式,至少first,second两个匹配才行.
          //2.first 匹配第一个 second 匹配第二个, rest 匹配剩余部分(5,9)
          case first :: second :: rest => println(first + second + rest.length) //
          case _ => println("匹配不到...")
        }
    

    底层代码:

        Predef..MODULE$.println("hello~~");
        List localList1 = List..MODULE$.apply(Predef..MODULE$.wrapIntArray(new int[] { 1, 3, 5, 9 }));
        if ((localList1 instanceof .colon.colon))
        {
          .colon.colon localcolon1 = (.colon.colon)localList1;int first = BoxesRunTime.unboxToInt(localcolon1.head());List localList2 = localcolon1.tl$1();
          if ((localList2 instanceof .colon.colon))
          {
            .colon.colon localcolon2 = (.colon.colon)localList2;int second = BoxesRunTime.unboxToInt(localcolon2.head());List rest = localcolon2.tl$1();Predef..MODULE$.println(BoxesRunTime.boxToInteger(first + second + rest.length()));localBoxedUnit = BoxedUnit.UNIT; return;
          }
        }
        Predef..MODULE$.println("��������...");BoxedUnit localBoxedUnit = BoxedUnit.UNIT;
    
    

    匹配嵌套结构

    scala> abstract class Item
    defined class Item
    
    scala> case class Book(description: String, price: Double) extends Item
    defined class Book
    
    scala> case class Bundle(description: String, discount: Double, item: Item*) extends Item
    defined class Bundle
    

    表示有一捆数,单本漫画(40-10) +文学作品(两本书)(80+30-20) = 30 + 90 = 120.0

    scala> val sale = Bundle("书籍", 10,  Book("漫画", 40), Bundle("文学作品", 20, Book("《阳关》", 80), Book("《围城》", 30)))
    sale: Bundle = Bundle(书籍,10.0,WrappedArray(Book(漫画,40.0), Bundle(文学作品,20.0,WrappedArray(Book(《阳关》,80.0), Book(《围城》,30.0)))))
    
    • 知识点1-将desc绑定到第一个Book的描述

     分析就是要取出嵌套结构中的 "漫画"

     如果我们进行对象匹配时,不想接受某些值,则使用_ 忽略即可,_* 表示所有

    scala> val res = sale match  {case Bundle(_, _, Book(desc, _), _*) => desc}
    res: String = 漫画
    • 知识点2-通过@表示法将嵌套的值绑定到变量

    把 "漫画" 和 boundle(文学作品...) 绑定到一个变量上,即赋值到变量中.

    scala> val result2 = sale match {case Bundle(_, _, cartoon @ Book(_, _), rest @ _*) => (cartoon,rest)}
    result2: (Book, Seq[Item]) = (Book(漫画,40.0),WrappedArray(Bundle(文学作品,20.0,WrappedArray(Book(《阳关》,80.0), Book( 《围城》,30.0)))))
    
    scala> println(result2)
    (Book(漫画,40.0),WrappedArray(Bundle(文学作品,20.0,WrappedArray(Book(《阳关》,80.0), Book(《围城》,30.0)))))
    
    scala> println("art =" + result2._1)
    art =Book(漫画,40.0)
    
    scala> println("rest=" + result2._2)
    rest=WrappedArray(Bundle(文学作品,20.0,WrappedArray(Book(《阳关》,80.0), Book(《围城》,30.0))))
    • 知识点3-不使用_*绑定剩余Item到rest

    因为没有使用 _* 即明确说明没有多个Bundle,所以返回的rest,就不是WrappedArray了。

    scala> val result2 = sale match {case Bundle(_, _, art @ Book(_, _), rest) => (art, rest)}
    result2: (Book, Item) = (Book(漫画,40.0),Bundle(文学作品,20.0,WrappedArray(Book(《阳关》,80.0), Book(《围城》,30.0))))
    
    scala> println(result2)
    (Book(漫画,40.0),Bundle(文学作品,20.0,WrappedArray(Book(《阳关》,80.0), Book(《围城》,30.0))))
    
    scala> println("art =" + result2._1)
    art =Book(漫画,40.0)
    
    scala> println("rest=" + result2._2)
    rest=Bundle(文学作品,20.0,WrappedArray(Book(《阳关》,80.0), Book(《围城》,30.0)))
    
    • 知识点4-递归调用

    请计算sale的销售总额

    scala>   def price(it:Item): Double = {
         |     it match  {
         |       case Book(_,p) => p
         |       case Bundle(_,disc,its @ _*) => its.map(price).sum - disc
         |     }
         |   }
    price: (it: Item)Double
    
    scala> println("price=" + price(sale))
    price=220.0
    

    密封类

    基本介绍

    如果想让case类的所有子类都必须在申明该类的相同的源文件中定义,可以将样例类的通用超类声明为sealed,这个超类称之为密封类。

    密封就是不能在其他文件中定义子类。

  • 相关阅读:
    HDU 1010 Tempter of the Bone
    HDU 4421 Bit Magic(奇葩式解法)
    HDU 2614 Beat 深搜DFS
    HDU 1495 非常可乐 BFS 搜索
    Road to Cinema
    Sea Battle
    Interview with Oleg
    Spotlights
    Substring
    Dominating Patterns
  • 原文地址:https://www.cnblogs.com/wqbin/p/13132202.html
Copyright © 2011-2022 走看看