Detailed explanation of Scala pattern matching

Big data technology AI

Flink/Spark/Hadoop / data warehouse, data analysis, interview, source code interpretation and other dry goods learning materials

101 original content

official account

Pattern matching in Scala is similar to the switch syntax in Java

 int i = 10
 switch (i) {
  case 10 :
 System.out.println("10");
 break;
  case 20 : 
 System.out.println("20");
 break;
  default : 
 System.out.println("other number");
 break;
 }

But scala is more powerful because it adds more functionality from the syntax.

1 basic grammar

  • In pattern matching syntax, match keyword is used to declare

  • Each branch is declared with the case keyword

  • When a match is needed, it starts with the first case branch

  • If the match is successful, the corresponding logical code is executed

  • If the match 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

 val a = 25
 val b = 13

 def matchDualOp(op: Char): Int = op match {
   case '+' => a + b
   case '-' => a - b
   case '*' => a * b
   case '/' => a / b
   case '%' => a % b
   case _ => -1
 }

 println(matchDualOp('+'))
 println(matchDualOp('/'))
 println(matchDualOp('\\'))
  • explain:

(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 block, and the code until the next case statement is executed as a whole,

You can use {} to enclose or not.

2 mode guard

  • explain

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

  • Case practice
 def abs(num: Int): Int = {
   num match {
     case i if i >= 0 => i
     case i if i < 0 => -i
   }
 }

 println(abs(67))

3 pattern matching type

3.1 matching constants

 def test(x: Any): String = x match {
   case 1 => "Int one"
   case "hello" => "String hello"
   case true => "Boolean true"
   case '+' => "Char +"
   case _ => ""
 }

 println(test("hello"))
 println(test('+'))
 println(test(0.3))
 String hello
 Char +

3.2 matching 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.

 def test(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(",")
   //Generic erase
   case m: List[_] => "List"+m
   case a => "else: " + a
 }

 println(test(35))
 println(test("hello"))
 //Generic erase
 println(test(List("hi", "hello")))
 //Generic erase
 println(test(List(2, 23)))
 println(test(Array("hi", "hello")))
 println(test(Array(2, 23)))
 Int 35
 String hello
 List List(hi, hello)
 List List(2, 23)
 else: [Ljava.lang.String;@6e8dacdf
 Array[Int] 2,23

3.3 matching array

scala pattern matching can accurately match collections, such as matching the first element with only two elements

Array with 0.

 for (arr <- List(
   Array(0),
   Array(1, 0),
   Array(0, 1, 0),
   Array(1, 1, 0),
   Array(2, 3, 7, 15),
   Array("hello", 1, 30)
 )) {
   val result = arr match {
     case Array(0) => "0"
     case Array(1, 0) => "Array(1, 0)"
      // Match two element array
     case Array(x, y) => "Array: " + x + ", " + y   
     case Array(0, _*) => "Array starting with 0"
     case Array(x, 1, z) => "Three element array with 1 in the middle"
     case _ => "else"
   }

   println(result)
 }
 0
 Array(1, 0)
 Array starting with 0
 Three element array with 1 in the middle
 else
 Three element array with 1 in the middle

3.4 matching list

Mode 1:

 for (list <- List(
   List(0),
   List(1, 0),
   List(0, 0, 0),
   List(1, 1, 0),
   List(88),
   List("hello")
 )) {
   val result = list match {
     case List(0) => "0"
     case List(x, y) => "List(x, y): " + x + ", " + y
     case List(0, _*) => "List(0, ...)"
     case List(a) => "List(a): " + a
     case _ => "else"
   }
   println(result)
 }
 0
 List(x, y): 1, 0
 List(0, ...)
 else
 List(a): 88
 List(a): hello

Mode 2:

 val list = List(1, 2, 5, 7, 24)
 //val list = List(24)

 list match {
   case first :: second :: rest => 
   println(s"first: $first, second: $second, rest: $rest")
   case _ => println("else")
 }
 first: 1, second: 2, rest: List(5, 7, 24)

3.5 matching tuples

 for (tuple <- List(
   (0, 1),
   (0, 0),
   (0, 1, 0),
   (0, 1, 1),
   (1, 23, 56),
   ("hello", true, 0.5)
 )){
   val result = tuple match {
     case (a, b) => "" + a + ", " + b
      //Is a tuple whose first element is 0
     case (0, _) => "(0, _)"
      //Is a tuple whose second element is 1
     case (a, 1, _) => "(a, 1, _) " + a
     case (x, y, z) => "(x, y, z) " + x + " " + y + " " + z
     case _ => "else"
   }
   println(result)
 }
 a 12
 b 35
 c 27
 a 13
 a: 12
 b: 35
 c: 27
 a: 13

3.6 matching objects

 object test {
   def main(args: Array[String]): Unit = {
     val student = Student("Outlaw maniac, Zhang San", 18)

     // Match the contents of the object instance
     val result = student match {
       case Student("Outlaw maniac, Zhang San", 18) => "Outlaw maniac, Zhang San, 18"
       case _ => "Else"
     }

     println(result)
   }
 }

 // Define class
 class Student(val name: String, val age: Int)

 // Define companion objects
 object Student {
   def apply(name: String, age: Int): Student = new Student(name, age)
   // You must implement an unapply method to disassemble object properties
   def unapply(student: Student): Option[(String, Int)] = {
     if (student == null){
       None
     } else {
       Some((student.name, student.age))
     }
   }
 }
  • val student = Student("outlaw, Zhang San", 11). When this statement is executed, it actually calls the * * * * apply method in the Student's associated object, so the corresponding object can be constructed without the new keyword.

  • When Student("outlaw maniac, Zhang San, 11") is written after case [case Student("outlaw maniac, Zhang San, 11) = >" outlaw maniac, Zhang San, 18 "], the unapply method (object extractor) will be called by default. Student is used as the parameter of the unapply method. The unapply method extracts the name and age attributes of the student object, Match the attribute value in Student("outlaw maniac, Zhang San", 11)

  • 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 if None is returned, the matching fails.

  • 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]]

3.7 sample classes

 object test {
   def main(args: Array[String]): Unit = {
     val user = User("zhangsan", 18)

     // Match the contents of the object instance
     val result = user match {
       case User("zhangsan", 18) => "zhangsan, 18"
       case _ => "Else"
     }

     println(result)
   }
 }

 // Define sample classes
 case class User(name: String, age: Int)
  • The sample class is still a class. Compared with ordinary classes, it only automatically generates associated objects, and some common methods are automatically provided in the associated objects, 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. Therefore, the sample class can directly use pattern matching 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)

View bytecode file:

User$:

 package com.duo;

 import scala.None.;
 import scala.Option;
 import scala.Serializable;
 import scala.Some;
 import scala.Tuple2;
 import scala.runtime.AbstractFunction2;
 import scala.runtime.BoxesRunTime;

 public final class User$ extends AbstractFunction2<String, Object, User>
   implements Serializable
{
   public static final  MODULE$;

   static
   {
     new ();
   }

   public final String toString()
{
     return "User"; } 
   public User apply(String name, int age) { return new User(name, age); } 
   public Option<Tuple2<String, Object>> unapply(User x$0) { return x$0 == null ? None..MODULE$ : new Some(new Tuple2(x$0.name(), BoxesRunTime.boxToInteger(x$0.age()))); } 
   private Object readResolve() { return MODULE$; } 
   private User$() { MODULE$ = this; }

 }

User:

import scala.Function1;
 import scala.Option;
 import scala.Product;
 import scala.Product.class;
 import scala.Serializable;
 import scala.Tuple2;
 import scala.collection.Iterator;
 import scala.reflect.ScalaSignature;
 import scala.runtime.BoxesRunTime;
 import scala.runtime.ScalaRunTime.;
 import scala.runtime.Statics;

 @ScalaSignature(bytes="\006\001\005....")
 public class User
   implements Product, Serializable
{
   private final String name;
   private final int age;

   public static Option<Tuple2<String, Object>> unapply(User paramUser)
   {
     return User..MODULE$.unapply(paramUser);
   }

   public static User apply(String paramString, int paramInt)
{
     return User..MODULE$.apply(paramString, paramInt);
   }

   public static Function1<Tuple2<String, Object>, User> tupled()
   {
     return User..MODULE$.tupled();
   }

   public static Function1<String, Function1<Object, User>> curried()
   {
     return User..MODULE$.curried();
   }

   public String name()
{
     return this.name; 
   } 
   public int age() { return this.age; } 

   public User copy(String name, int age) { return new User(name, age); } 

   public String copy$default$1() { return name(); } 
   public int copy$default$2() { return age(); } 

   public String productPrefix() { return "User"; } 

  public int productArity() {
     return 2;
   }
   public Object productElement(int x$1) {
     int i = x$1;
     switch(i) {
       default:
       throw new IndexOutOfBoundsException(BoxesRunTime.boxToInteger(x$1).toString());
       case 1:
         break;
       case 0:
     }
     return name();
   }
   public Iterator < Object > productIterator () {
     return ScalaRunTime..MODULE$.typedProductIterator(this);
   }
   public boolean canEqual(Object x$1) {
     return x$1 instanceof User;
   }

   public int hashCode() {
     int i = -889275714; i = Statics.mix(i, Statics.anyHash(name())); i = Statics.mix(i, age()); return Statics.finalizeHash(i, 2);
   }

   public String toString() {
     return ScalaRunTime..MODULE$._toString(this);
   }

   public boolean equals(Object x$1) {
     if (this != x$1) {
       Object localObject = x$1;
       int i;
       if ((localObject instanceof User)) i = 1; else i = 0;
       if (i == 0) break label96;
       User localUser = (User) x$1;
       str = localUser.name();
       String tmp42_32 = name();
       if (tmp42_32 == null) {
         tmp42_32; if (str == null) break label63; tmpTernaryOp = tmp42_32; break label88;
       }
     }
   }
   public User (String name, int age) {
     Product
     .class.$init$(this);
   }
 }

4 pattern matching in variable declaration

 val (x, y) = (10, "hello")
 println(s"x: $x, y: $y")

 val List(first, second, _*) = List(23, 15, 9, 78)
 println(s"first: $first, second: $second")

 val fir :: sec :: rest = List(23, 15 , 9, 78)
 println(s"first: $fir, second: $sec, rest: $rest")

5. Pattern matching in the derivation

 val list: List[(String, Int)] = List(("a", 12), ("b", 35), ("c", 27), ("a", 13))

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

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

 // 3. You can traverse only key or value without considering the variables at a certain position
 for ((word, _) <- list)
   println(word)


 // 4 you can specify what the value of a location must be
 for (("a", count) <- list){
   println(count)
 }

 // 5 cycle guard
  for ((k, v) <- map if v >= 1) {
  println(k + " ---> " + v) 
  }

6 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 implemented

(1) Partial function definition

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

(2) Partial function 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)
  } 
  }

(3) Partial function use

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 and the default method

Processing logic for parameters that do not meet the requirements.

(4) Case practice

Say the second element * 2 of tuple in list

 val list: List[(String, Int)] = List(("a", 12), ("b", 35), ("c", 27), ("a", 13))
 
 // 1. map conversion, realizing that the key remains unchanged and the value is 2 times
 val newList = list.map( tuple => (tuple._1, tuple._2 * 2) )
 
 // 2. Use pattern matching to assign values to tuple elements to realize the function
 val newList2 = list.map(
   tuple => {
     tuple match {
       case (word, count) => (word, count * 2)
     }
   }
 )
 
 // 3. Omit the writing method of lambda expression and simplify it
 val newList3 = list.map {
       case (word, count) => (word, count * 2)
   }

Keywords: Java Scala Big Data

Added by Altairzq on Sat, 01 Jan 2022 03:26:02 +0200