Scala pattern matching

Posted by phprock on Mon, 27 Dec 2021 06:37:12 +0100

Basic grammar

The pattern matching in scala is similar to the switch syntax in Java, but Scala adds more functions from the syntax, so it is more powerful. In the pattern matching syntax, the match keyword is used to declare, and each branch is declared with the case keyword. When matching is needed, it will start from the first case branch. If the matching is successful, the corresponding logic code will be executed. If the matching is unsuccessful, continue to execute the next branch for judgment. If all cases do not match, the case is executed_ Branch, similar to the default statement in Java.

    //Basic grammar
    val x: Int = 2 //Define a variable
    val y: String = x match {  //The second variable matches the first

      case Condition 1 => "Result 1"
      case Condition 2 => "Result 2"
      case Condition 3 => "Result 3"
      case _ => "other"
    }

among
(1) If all cases do not match, the case branch will be executed, which is similar to the default statement in Java,
If there is no case at this time_ Branch, then throws MatchError.
(2) In each case, you do not need to use the break statement to automatically interrupt the case.
(3) The match case statement can match any type, not just literal.
(4) = > the following code blocks until the next case statement are executed as a whole, which can
Use {} to enclose or not.

Declaration of pattern matching

(1) Match on variable declaration
(2) Match in for expression

package chapter08

object Test03_MatchTupleExtend {

  def main(args: Array[String]): Unit = {

    //Match at variable declaration
    val (x, y) = (10,"hello")
    println(s"x: $x, y: $y ")

    val List(first, second, rest) = List(23,24,25)

    println(s"first: $first, second: $second, rest: $rest")


    val List(fir, sec, _*) = List(23,24,25,26)
    println(s"fir: $fir, sec: $sec")

    val firs :: seco :: res = List(23,24,25,26)
    println(s"fir: $fir, sec: $sec, rest:$res")

    //Pattern matching in for derivation
    val list: List[(String,Int)] = List(("a",1),("b",2),("c",3),("a",4))

    //Original traversal mode
    for (elem <- list){
      println(elem._1 + " " + elem._2)
    }

    //Define the elements of the List as tuples and assign values to variables
    for ((word,count) <- list){
      println(word + " " + count)
    }

    //Do not consider variables at a certain location, only traverse key or value
    for ((word,count) <- list){
      println(word)
    }

    //A traversal that specifies what the value of a location must be
    for (("a",count) <- list){
      println(count)
    }

  }

}

Mode guard

If you want to express data that matches a certain range, you need to add conditional guard in pattern matching.

  //Mode guard
    //Find the absolute value of an integer
    def abs(num:Int): Int = {
      num match {
        case i if i >= 0 => i
        case i if i <= 0 => -i
      }
    }

    println(abs(78))
    println(abs(-78))

Pattern matching type

Match constant

In Scala, pattern matching can match all literal quantities, including strings, characters, numbers, Boolean values, and so on.

Match type

When type judgment is required, you can use isInstanceOf[T] and asInstanceOf[T] learned above, or
Use pattern matching to achieve the same function.

Matching set

scala pattern matching can accurately match collections. Such as arrays, lists, tuples, etc.

package chapter08

object Test02_MathTypes {

  def main(args: Array[String]): Unit = {

    //Match constant
    def describeConst(x: Any): String = x match {

      case 1 => "int one"
      case "hello" => "string hello"
      case true => "Boolean true"
      case '+' => "char +"
      case _ => "other"
    }

    println(describeConst("hello"))
    println(describeConst('+'))
    println(describeConst(0.3))


    //Type matching
    def describeType(x: Any): String = x match {

      case i: Int => "int "+i
      case s: String => "string " + s
      case list: List[String] => "List " + list
      case array: Array[Int] => "Array[Int] " + array.mkString(",")
      case a => "something else: " + a

    }

    println(describeType(3))
    println(describeType("s"))
    println(describeType(List(1,2)))
    println(describeType(Array(1,2)))
    println(describeType(List("1,2")))


    //Matching array
    for(arr <- List(
      Array(0),
      Array(1,2,3),
      Array(1,2,"hello"),
      Array(1,2,15),
      Array(1,2),

    )){
      val result = arr match {
        case Array(0)=> "0"
        case Array(1,2,3) => "Array(1,2,3)"
        case Array(x,y) => "Array: " + x + ", " + y //Match two element array
        case Array(1,_*) => "Array starting with 1"
        case Array(x, 2, y) => "Three element array with 2 in the middle"
        case _ => "something else"

      }
     println(result)
    }


    //List matching
    for(list <- List(
      List(0),
      List(0,1),
      List(1,1,0),
      List(1,1,1),
      List(88),
    )){
      val result = list match {

        case List(0) => "0"
        case List(x,y) => "list(x + y)" + x +"," + y
        case List(1,_*) => "list(1,...)"
        case List(a) => "list(a)" + a
        case _ => "something else"
      }

      println(result)
    }

    //Mode II
    val list = List(1,2,3,45,12)
    val list2 = List(1)

    list2 match {
      case first :: second :: rest => println(s"first: $first, second: $second, rest: $rest")
      case _ => println("something else")
    }


    println("============")
    //Matching tuple
    for (tuple <- List(
      (0,1),
      (1,2,3),
      (0,1,2),
      ("hello",1),
    )){
      val reuslt = tuple match {
        case (a,b) => "" + a + "," +b
        case (0,_) => "(0,-)" //No (0, *), because the tuple size is fixed
        case (a, 1, _ ) => "()"
        case _ => "something else"

      }

      println(reuslt)
    }
  }

}

Matching objects and sample classes

Match object

Case practice

class User(val name: String, val age: Int) 
object User{ 
 
    def apply(name: String, age: Int): User = new User(name, age) 
 
    def unapply(user: User): Option[(String, Int)] = { 
        if (user == null) 
            None 
        else 
            Some(user.name, user.age) 
    } 
} 
 
object TestMatchUnapply { 
    def main(args: Array[String]): Unit = { 
        val user: User = User("zhangsan", 11) 
        val result = user match { 
            case User("zhangsan", 11) => "yes" 
            case _ => "no" 
        } 
 
        println(result) 
    } 
} 

explain
(1) val user = User("zhangsan", 11). When this statement is executed, it actually calls the apply method in the User associated object, so the corresponding object can be constructed without the new keyword.
(2) When User("zhangsan", 11) is written after case [case User("zhangsan", 11) = > "yes"], the unapply method (object extractor) will be called by default. User is the parameter of the unapply method, and the unapply method
Extract the name and age attributes of the user object and compare them with the attribute values in the User("zhangsan", 11)
matching
(2) If the unapply method (extractor) of the object in case returns Some and all properties are consistent, the matching is successful,
If the properties are inconsistent or None is returned, the matching fails.
(3) If only one attribute of the object is extracted, the extractor is unapply(obj:Obj):Option[T]
If multiple attributes of an object are extracted, the extractor is unapply(obj:Obj):Option[(T1,T2,T3...)]
If variable attributes of an object are extracted, the extractor is unapplySeq(obj:Obj):Option[Seq[T]]

Sample class

Basic introduction

(1) Syntax:

 case class Person (name: String, age: Int)  //give an example

(2) Explain

  • The sample class is still a class. Compared with the ordinary class, it only automatically generates the companion object, and it is in the companion object
    Some common methods are provided automatically, such as apply, unapply, toString, equals, hashCode and copy.
  • The sample class is optimized for pattern matching because it provides the unapply method by default
    Class can use pattern matching directly without implementing the unapply method itself.
  • Every parameter in the constructor becomes a val unless it is explicitly declared as var (this is not recommended)

Case practice

Using the sample class in the case of matching objects above will save a lot of code

case class User(name: String, age: Int) 
 
object TestMatchUnapply { 
    def main(args: Array[String]): Unit = { 
        val user: User = User("zhangsan", 11) 
        val result = user match { 
            case User("zhangsan", 11) => "yes" 
            case _ => "no" 
        } 
 
        println(result) 
    } 
} 

Pattern matching in partial function

Partial function is also a kind of function. Through partial function, we can easily check the input parameters more accurately. for example
The input type of the partial function is List[Int], and what we need is a set whose first element is 0, which is through the pattern
Matching implementation.

Definition of partial function

Basic grammar

val second: PartialFunction[List[Int], Option[Int]] = { 
    case x :: y :: _ => Some(y) 
}


Note: the function of this partial function is to return the second element of the input List set

principle

The above code will be translated into the following code by the scala compiler. Compared with ordinary functions, there is only one more parameter
The function to be checked -- isDefinedAt, whose return value type is Boolean.

val second = new PartialFunction[List[Int], Option[Int]] { 
 
    //Check whether the input parameters are qualified 
    override def isDefinedAt(list: List[Int]): Boolean = list match 
{ 
        case x :: y :: _ => true 
        case _ => false 
    } 
 
    //Execute function logic 
    override def apply(list: List[Int]): Option[Int] = list match 
{ 
        case x :: y :: _ => Some(y) 
    } 
} 

Use of partial functions

Partial functions cannot be used directly like second(List(1,2,3)), because this will directly call the apply method, instead of
This calls the applyOrElse method as follows

second.applyOrElse(List(1,2,3), (_: List[Int]) => None) 

The logic of the applyOrElse method is if (ifDefinedAt(list)) apply(list) else default. If the input parameter is full
If the condition is sufficient, that is, isDefinedAt returns true, execute the apply method; otherwise, execute the defaultut method. The default method is the processing logic whose parameters do not meet the requirements.

Case practice

package chapter08

object Test06_PartialFunction {
  def main(args: Array[String]): Unit = {

    val list: List[(String,Int)] = List(("a",1),("b",2),("c",3),("a",4))

    //Application of partial function to find the absolute value
    //The input data is divided into different situations: positive, negative and zero
    val positiveAbs: PartialFunction[Int,Int] = {
      case x if x > 0 => x
    }

    val negativeAbs: PartialFunction[Int,Int] = {
      case x if x < 0 => -x
    }

    val zeroAbs: PartialFunction[Int,Int] = {
      case 0 => 0
    }

    def abs(x:Int): Int = (positiveAbs orElse negativeAbs orElse zeroAbs) (x)

    println(abs(13))
    println(abs(0))
    println(abs(-14))
  }

}

Topics: Scala Programming