Scala Tips: Matching

Scala matching functionality is compelling. This feature is very important for big data programming. This blog tries to consolidate matching example as much as possible.



Basic use of Match

The simplest matching

var  i = 2           // i: Int = 2        
                     //                   
i match {            // res0: String = two
    case 1 => "one"  // 
    case 2 => "two"  // 
}                    // 

You can match anything as follows:

def getClassAsString(x: Any):String = x match {  // getClassAsString: (x: Any)String 
    case s: String => s + " is a string"         //                                  
    case i: Int => i +" is Int"                  //                                  
    case f: Float => "Float"                     //                                  
}                                                //                                  
                                                 //                                  
getClassAsString(1)                              // res0: String = 1 is Int          
getClassAsString("J")                            // res1: String = J is a string     

Boolean operator

Use of the boolean operator1

def isTrue(a: Any) = a match {   // isTrue: (a: Any)Boolean
  case 0 | "" => false           //                        
  case _ => true                 //                        
}                                //                        
                                 //                        
isTrue(0)                        // res0: Boolean = false  
isTrue("")                       // res1: Boolean = false  
isTrue(1.1F)                     // res2: Boolean = true   

Use in the exceptions

Use in the exceptions:

try {                                                                     // cannot divide by zero: java.lang.ArithmeticException: / by zero  
  1/0                                                                     // res0: AnyVal = ()                                                
} catch {                                                                 // 
  case a: ArithmeticException =>  println(s"cannot divide by zero: $a")   // 
  case e: RuntimeException => println(s"cannot proceed because $e")       // 
  case _: Throwable => println("Unexpected exception.")                   // 

With Constants

appear in backticks

val C =2
val v = 3 

(1,2,3) match {
  case (1,C,`v`) => "this is matched"
} 
// res16: String = "this is matched"

(1,2,3) match {
  case (1,`C`,`v`) => "this is matched"
} 

// res17: String = "this is matched"

With Regex

Simple regex example:

val re = """a+b+""".r 
"aaabbb" match {
  case re => "match found!"
} // res3: String = "match found!"

By using parentheses, you can indicate groups in the pattern that are to be captured

val adv_re = """(\d+), (\S+) \S+ (\S+).*""".r
"10, is the best" match {
  case adv_re(a,b,c) => f"matches with a=$a b=$b c=$c"
} 
// res8: String = "matches with a=10 b=is c=best"

With Vector

Simply

val a = Vector (1,2,3) 
a match{
  case Vector(1,2) => println("(1,2) matched")
  case Vector(1,2,3) => println("(1,2,3) matched")
} // (1,2,3) matched

With Map

Another good example of matching is :

val l1 = Seq(1,2,3,4)
l1.map {
  case first if first == 1 => "first"
  case second if second == 2 => "second"
  case third if third == 3 => "three"
  case fourth if fourth == 4 => "four"
}

As shown in the above you don't need match keyword.

As an Iterator

To iterate over Map

val states = Map(                            // states: scala.collection.immutable.Map[String,String] = Map(NSW -> New South Wales, ATC -> Australian Capital Territory, VIC -> Victoria)  
  "NSW" -> "New South Wales",                //                                                                                                                                            
  "ATC" -> "Australian Capital Territory",   //                                                                                                                                            
  "VIC" -> "Victoria"                        //                                                                                                                                            
)                                            //                                                                                                                                            
                                             //                                                                                                                                            
states.foreach {                             // NSW is New South Wales                                                                                                                     
  case(k, v) => println(s"$k is $v")         // ATC is Australian Capital Territory                                                                                                        
}                                            // VIC is Victoria                                                                                                                            

Case with guard conditions

Here the match command with the gurde conditions:

def dayTime (actualTime: Int):String = { 
  actualTime match {
    case morning if morning <= 12 => "Morning"
    case afternoon if (afternoon > 12 && afternoon <= 18) => "Afternoon"
    case night if night > 18 && night < 23 => "Night"
    case invalid => "invalid"

  }
}

dayTime(20)  //res1: String = Night
dayTime(10)  //res1: String = Morning   

With Option

Important to notice that Option has a different meaning for None based on the Some(). For example, Some(None) is not actually None.

val opt_none1: Option[String] = None              // opt_none1: Option[String] = None             
val opt_none2: Option[None.type ] = Some(None)    // opt_none2: Option[None.type] = Some(None)    
                                                  //                                              
opt_none1 match {                                 // No one found                                 
  case Some(thing) => println(s"found ${thing}")  // res1: Unit = ()                              
  case None => println("No one found")            //                                              
}                                                 //                                              
                                                  //                                              
opt_none2 match {                                 // found None                                   
  case Some(thing) => println(s"found ${thing}")  // res2: Unit = ()                              
  case None => println("No one found")            //                                              
}                                                 //                                              

As shown in the above Some(None) is not None.

Another use case of the Option/Some is

val oo_lang : Option[String] = Some("Java")
val functional_lang : Option[String] = Some("Scala")
val default_lang : Option[String] = None

val current_lang = default_lang orElse functional_lang orElse oo_lang getOrElse "No Language"

The result is Scala, here orElse play the big role. This will return the first None value.

With Tuples

Using case:

val person : (String, Int, String) = ("Katy",23, "test") // person: (String, Int, String) = (Katy,23,test)
                                                         //                                               
person match {                                           // res0: String = Katy is 23 old!                
  case (para1, para2, para3) => s"$para1 is $para2 old!" //                                               
}                                                        //                                               

combining case and Option/Some for matching:

val parrot: (String, Option[String]) = ("parrot",Some("speak"))                    // parrot: (String, Option[String]) = (parrot,Some(speak))                         
val tiger: (String, Option[String]) = ("tiger", Some("run"))                       // tiger: (String, Option[String]) = (tiger,Some(run))                             
                                                                                   //                                                                                 
def getBehavior(animal: (String, Option[String])) : String = {                     // getBehavior: getBehavior[](val animal: (String, Option[String])) => String      
                                                                                   //                                                                                 
  animal match {                                                                   //                                                                                 
    case (name, None) => s"${name} has No behavior"                                //                                                                                 
    case (name, Some(behavior)) if behavior == "speak" => s"$name can $behavior"   //                                                                                 
    case (name, Some(behavior)) if behavior == "run" => s"$name can $behavior"     //                                                                                 
  }                                                                                //                                                                                 
}                                                                                  //                                                                                 
                                                                                   //                                                                                 
getBehavior(parrot)                                                                // res1: String = parrot can speak                                                 
getBehavior(tiger)                                                                 // res2: String = tiger can run
getBehavior(("cat", Some("run")))                                                  // res3: String = cat can run

In the above code, if conditions are the guard conditions which will avoid the if the else construction can happen under the one case statement.

For example, see the combination of Person and Gender case classes2:

case class Gender(gender: Boolean)                          // defined class Gender                       
                                                            //                                            
case class Person (name: String, age:Int, gender: Gender)   // defined class Person                       
val p1:Person = Person("Ketty",29, Gender(true))            // p1: Person = Person(Ketty,29,Gender(true)) 
                                                            //                                            
p1 match {                                                  // res0: String = Ketty is 29 old! man        
  case Person(name, age, Gender(true)) =>                   //                                            
    s"$name is $age old! man"                               //                                            
  case Person(name, age, Gender(false)) =>                  //                                            
    s"$name is $age old! woman"                             //                                            
}                                                           //                                            
                                                            //                                            
                                                            //                                            
val p2:Person = Person("John",29, Gender(true))             // p2: Person = Person(John,29,Gender(true))  
                                                            //                                            
p2 match {                                                  // res1: String = John is 29 old! man         
  case Person(name, age, Gender(true)) =>                   //                                            
    s"$name is $age old! man"                               //                                            
  case Person(name, age, Gender(false)) =>                  //                                            
    s"$name is $age old! woman"                             //                                            
}                                                           //                                            
                                                            //                                            
                                                            //                                            
//add the gurd conditions                                   //                                            
val p3:Person = Person("John",29, Gender(true))             // p3: Person = Person(John,29,Gender(true))  
                                                            //                                            
p3 match {                                                  // res2: String = John is 29 old! man         
  case Person(name, age, Gender(true)) =>                   //                                            
    s"$name is $age old! man"                               //                                            
  case Person(name, age, Gender(false)) =>                  //                                            
    s"$name is $age old! woman"                             //                                            
}                                                           //                                            
                                                            //                                            
//clone the existing case class                             //                                            
val p4 = p2.copy(name="Mike")                               // p4: Person = Person(Mike,29,Gender(true))  
                                                            //                                            
//direct pattern matching                                   //                                            
val Person(p3_name, _, Gender(p3_gender)) = p3              // p3_name: String = John                     
                                                            // p3_gender: Boolean = true                  
                                                            //                                            
println(s"p3 Gender: $p3_gender and p3 Name:: $p3_name")    //                                            
                                                            // p3 Gender: true and p3 Name:: John         
                                                            // res3: Unit = ()                            

You can use the nested case statement as follows:

val parrot: (String, Option[String]) = ("parrot",Some("speak"))           // parrot: (String, Option[String]) = (parrot,Some(speak))                                 
val tiger: (String, Option[String]) = ("tiger", Some("run"))              // tiger: (String, Option[String]) = (tiger,Some(run))                                     
                                                                          //                                                                                         
                                                                          //                                                                                         
def getBehaviorNested(animal: (String, Option[String])) : String = {      // getBehaviorNested: getBehaviorNested[](val animal: (String, Option[String])) => String  
                                                                          //                                                                                         
    animal match {                                                        //                                                                                         
      case (name, behavior) =>                                            //                                                                                         
        val desc : String  =                                              //                                                                                         
          behavior match {                                                //                                                                                         
            case None => s" nothing"                                      //                                                                                         
            case Some(behavior) if behavior == "speak" => s" $behavior"   //                                                                                         
            case Some(behavior) if behavior == "run" => s" $behavior"     //                                                                                         
          }                                                               //                                                                                         
        s"$name have behavior $desc"                                      //                                                                                         
  }                                                                       //                                                                                         
}                                                                         //                                                                                         
                                                                          //                                                                                         
getBehaviorNested(parrot)                                                 // res0: String = parrot have behavior  speak                                              
getBehaviorNested(tiger)                                                  // res1: String = tiger have behavior  run                                                 
getBehaviorNested(("cat", Some("run")))                                   // res2: String = cat have behavior  run                                                   

With List

With the collection, you can use match. For example, with the List:

val list:List[Int] = 1 :: 2 :: Nil                                                               // list: List[Int] = List(1, 2)                            
                                                                                                 //                                                         
list match {                                                                                     // res0: Int = 3                                           
  case List(a, b) => a + b                                                                       //                                                         
}                                                                                                //                                                         
                                                                                                 //                                                         
val list1 = list :+ 3                                                                            // list1: List[Int] = List(1, 2, 3)                        
                                                                                                 //                                                         
// defined the rest of the elements                                                              //                                                         
list1 match {                                                                                    //  first + second = 3, and rest : List(3)res1: Int = 3    
  case a :: b :: r => print (s" first + second = ${a +b}, and rest : $r"); a+b                   //                                                         
}                                                                                                //                                                         
                                                                                                 //                                                         
val list2 = list1 :+ 4                                                                           // list2: List[Int] = List(1, 2, 3, 4)                     
                                                                                                 //                                                         
// use of placeholder, even for the above `r` also _ can be used : see the next match example    //                                                         
list2 match {                                                                                    //  first + second = 5, and rest : List(4)res2: Int = 5    
  case _ :: a :: b :: r => print (s" first + second = ${a +b}, and rest : $r"); a+b              //                                                         
}                                                                                                //                                                         
                                                                                                 //                                                         
//add list to list                                                                               //                                                         
val list3 = list2 ::: 4 :: 5 :: 6 :: 7 :: Nil                                                    // list3: List[Int] = List(1, 2, 3, 4, 4, 5, 6, 7)         
//use placeholders and unpack only the necessary values to a and b variables                     //                                                         
list1 match {                                                                                    // res3: Int = 5                                           
  case _ :: a :: b :: _ => a+b                                                                   //                                                         
}                                                                                                //                                                         

With Seq

But sequences are different.

val seq:Seq[Int] = Seq(1,2,3,4,5,6)

seq match {
//  case Seq(a,b, _) => a +b //this is an error
  case Seq(a,b, r @ _*) => a + b
}

It is important to remember the r @ _* in the above source.

These are the notes created from the video tutorial3. I would like to recommend this for beginners who want more details.

Wildcards

Portion of the expression can match anything

(1,2,3) match {
  case (1,_,_) => "This will match any thing start with 1"
  case _ => "Match which is not matched with above"
  }
// res18: String = "This will match any thing start with 1"

Sequences can be matched without knowing exactly how many items there are:

Vector(1,2,3,4) match {
  case Vector(x,y,_*) => s"matches with $x,$y and ignore rest"
  } 
// res19: String = "matches with 1,2 and ignore rest"

Use above Vector(x,y,_*) in nested:

Vector((1,2), (3,4), (5,6)) match {
    case Vector((x, y), _*) => s"this will match with x=$x, y=$y"
  } 
// res23: String = "this will match with x=1, y=2"

Match a pattern and then bind the matched portion to a variable.

This is done with the @ symbol4:

Vector((1,2),(3,4), (5,6)) match {
    case a @ Vector((x,y),_*) => s"bind $a to `a`)"
    }  
// res36: String = "bind Vector((1,2), (3,4), (5,6)) to `a`)"

Bind second pair

Vector((1,2),(3,4), (5,6)) match {
    case Vector((1,2), a @ (x,y),_*) => s"bind $a to `a`"
    }  
res37: String = "bind (3,4) to `a`"

Bind tail

Vector((1,2),(3,4), (5,6)) match {
  case Vector((x,y), a @ _*) => s"bind $a to `a`"
  } 
// res39: String = "bind Vector((3,4), (5,6)) to `a`"

Case classes

case class A(i:Int, j:Int) 

A(1,2) match {
  case A(x,y) => s" this will match with i=$x and j=$y"
  } 
// res42: String = " this will match with i=1 and j=2"

Reference


  1. Match Expressions 

  2. Blog post Scala Tips: case class 

  3. Scala Beginner Programming Recipes, by Antonio Salazar Cardozo, Published by Packt, Publishing, 2017 

  4. Scala Basics 

Comments

Popular posts from this blog

How To: GitHub projects in Spring Tool Suite

Spring 3 Part 7: Spring with Databases

Parse the namespace based XML using Python