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
- Boolean operator
- Use in the exceptions
- With Constants
- With Regex
- With Vector
- With Map
- As an Iterator
- Case with guard conditions
- With Option
- With Tuples
- With List
- With Seq
- Wildcards
- Case classes
- Reference
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
-
Blog post Scala Tips: case class ↩
-
Scala Beginner Programming Recipes, by Antonio Salazar Cardozo, Published by Packt, Publishing, 2017 ↩
Comments
Post a Comment
commented your blog