Pattern Matching

1. Values, Variables, and Types in Matches

(1) Basic

Let’s cover several kinds of matches. The following example matches on specific values, all values of specific types, and it shows one way of writing a “default” clause that matches anything:

code example

object Hello {
  def main(args: Array[String]): Unit = {
    for (x <- Seq(1, 2, 2.7, "one", "two", 'four)) {
      val str = x match {
        case 1 => "int 1"
        case i: Int => "other int: "+i  // 假设匹配此句, i=x
        case d: Double => "a double: "+x
        case "one" => "string one"
        case s: String => "other string: "+s
        case unexpected => "unexpected value: "+unexpected
      }
      println(str)
    }
  }
}

Note:

  1. Because x is of type Any, we need enough clauses to cover all possible values. That’s why the “default” clause (with unexpected) is needed.
  2. Matches are eager(及早的), so more specific clauses must appear before less specific clauses.(如果case 1 => "int 1"case i: Int => "other int: "+i交换顺序,则case 1将永远不会匹配).
  3. Matching on floating-point literals is a bad idea, as rounding errors mean two values that appear to be the same often differ in the least significant digits.

(2) Using Placeholder _

We can replace the variables i, d, s, and unexpected with the placeholder _.

object Hello {
  def main(args: Array[String]): Unit = {
    for (x <- Seq(1, 2, 2.7, "one", "two", 'four)) {
      val str = x match {
        case 1 => "int 1"
        case _: Int => "other int: "+x
        case _: Double => "a double: "+x
        case "one" => "string one"
        case _: String => "other string: "+x
        case _ => "unexpected value: "+x
      }
      println(str)
    }
  }
}

(3) Using defined variable after case

In case clauses, a term that begins with a lowercase letter is assumed to be the name of a new variable that will hold an extracted value. To refer to a previously defined variable, enclose it in back ticks. Conversely, a term that begins with an uppercase letter is assumed to be a type name.

object Hello {
  def main(args: Array[String]): Unit = {
    checkY(100)
  }

  def checkY(y :Int): Unit = {
    for (x <- Seq(99, 100, 101)) {
      val str = x match {
        case `y` => "found y!"  // 如果你使用y,而不是`y`,则它并不是函数参数y 
        case i: Int => "int: "+i
      }
      println(str)
    }
  }
}

(4) Using |

Sometimes we want to handle several different matches with the same code body. To avoid duplication, we could refactor the code body into a method, but case clauses also support an “or” construct, using a | method:

code example

object Hello {
  def main(args: Array[String]): Unit = {
    for (x <- Seq(1, 1.2, 3, "haha")) {
      val str = x match {
        case _: Int | _: Double => "number: "+x
        case _ => "string: "+x
      }
      println(str)
    }
  }
}

output

number: 1
number: 1.2
number: 3
string: haha

2. Matching Sequences

(1) Matching Sequences

Seq (for “sequence”) is a parent type for the concrete collection types that support iteration over the elements in a deterministic order, such as List and Vector.

Let’s examine the classic idiom for iterating through a Seq using pattern matching and recursion, and along the way, learn some useful fundamentals about sequences:

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

    val nonEmptySeq = Seq(1, 2, 3, 4, 5)
    val emptySeq = Seq.empty[Int]
    val nonEmptyList = List(1, 2, 3, 4, 5)
    val emptyList = Nil
    val nonEmptyVector = Vector(1, 2, 3, 4, 5)
    val emptyVector = Vector.empty[Int]
    val nonEmptyMap = Map("one"->1, "two"->2, "three"->3)
    val emptyMap = Map.empty[String, Int]

    val newSeq = Seq(nonEmptySeq, emptySeq, nonEmptyList, emptyList, nonEmptyVector, emptyVector, nonEmptyMap.toSeq, emptyMap.toSeq)  // (4)
    for (seq <- newSeq){
      println(seqToString(seq))
    }
  }

  def seqToString[T](seq: Seq[T]): String = {  // (1)
    seq match {
      case head +: tail => s"$head +: "+seqToString(tail)  // (2)
      case Nil => "Nil"  // (3)
    }
  }
}

Explain:

  • (1)Define a recursive method that constructs a String from a Seq[T] for some type T. The body is one expression that matches on the input Seq[T]
  • (2)There are two match clauses and they are exhaustive. The first matches on any nonempty Seq, extracting the head, the first element, and the tail, which is the rest of the Seq. (Seq has head and tail methods, but here these terms are interpreted as variable names as usual for case clauses.) The body of the clause constructs a String with the head followed by +: followed by the result of calling seqToString on the tail.
  • (3)The only other possible case is an empty Seq. We can use the special object for empty Lists, Nil, to match all the empty cases. Note that any Seq can always be interpreted as terminating with an empty instance of the same type, although only some types, like List, are actually implemented that way.
  • (4)Put the Seqs in another Seq (calling toSeq on the Maps to convert them to a sequence of key-value pairs), then iterate through it and print the results of calling seqToString on each one.

Note:

  1. The operator +: is the “cons”(construction) operator for sequences. It is similar to the :: operator for Lists.
  2. The terms head and tail are arbitrary variable names. That’s mean that you can replace them with x and y.
  3. If your sequence is List,you can replace +: with ::. But it’s more conventional now to write code that uses Seq, so it can be applied to all subclasses, including List and Vector.

(2) Reconstruct List, Map

We can use +: and :: restruct List:

scala> val s1 = (1 +: (2 +: (3 +: (4 +: (5 +: Nil)))))
s1: List[Int] = List(1, 2, 3, 4, 5)

scala> val l = (1 :: (2 :: (3 :: (4 :: (5 :: Nil)))))
l: List[Int] = List(1, 2, 3, 4, 5)

scala> val s2 = (("one",1) +: (("two",2) +: (("three",3) +: Nil)))
s2: List[(String, Int)] = List((one,1), (two,2), (three,3), (four,4))

scala> val m = Map(s2 :_*)
m: scala.collection.immutable.Map[String,Int] = Map(one -> 1, two -> 2, three -> 3, four -> 4)

Note that the Map.apply factory method expects a variable argument list of two-element tuples. So, in order to use the sequence s2 to construct a Map, we had to use the :_*idiom for the compiler to convert it to a variable-argument list.

So, there’s an elegant symmetry between construction and pattern matching (“deconstruction”) when using +: and ::.

3. Matching on Tuples

Tuples are so easy to match on:

object Hello {
  def main(args: Array[String]): Unit = {
    val langs = Seq(
      ("Scala", "Martin", "Odersky"),
      ("Clojure", "Rich", "Hickey"),
      ("Lisp", "John", "McCarthy"))
    for (tuple <- langs) {
      tuple match {
        case ("Scala", _, _) => println("Found Scala")
        case (lang, firstName, lastName) => println(s"Found other language: $lang, $firstName $lastName")
      }
    }
  }
}

output

Found Scala
Found other language: Clojure, Rich Hickey
Found other language: Lisp, John McCarthy

A tuple can be taken apart into its constituent elements. We can match on literal values within the tuple, at any positions we want, and we can ignore elements we don’t care about.

4. Guards in case Clauses

Code Example:

object Hello {
  def main(args: Array[String]): Unit = {
    val list = List(1, 2, 3, 4, 5)
    for (value <- list) {
      value match {
        case _ if value%2 == 0 => println(s"even: $value")
        case _                 => println(s"odd: $value")
      }
    }
  }
}

Output:

odd: 1
even: 2
odd: 3
even: 4
odd: 5

5. Maching on case Classes

(1) Deep Matching

Let’s see more examples of deep matching, where we examine the contents of instances of case classes:

Code Example:

case class Address(street: String, city: String, country: String)
case class Person(name: String, age: Int, address: Address)

val alice = Person("Alice", 25, Address("1 Scala Lane", "Chicago", "USA"))
val bob = Person("Bob", 29, Address("2 Java Ave.", "Miami", "USA"))
val charlie = Person("Charlie", 32, Address("3 Python Ct.", "Boston", "USA"))

for (person <- Seq(alice, bob, charlie)) {
    person match {
        case Person("Alice", 25, Address(_, "Chicago", _) => println("Hi Alice!")
        case Person("Bob", 29, Address("2 Java Ave.", "Miami", "USA")) => println("Hi Bob!")
        case Person(name, age, _) => println(s"Who are you, $age year-old person named $name?")
    }
}

Output:

Hi Alice!
Hi Bob!
Who are you, 32 year-old person named Charlie?

Note that we could match into nested types.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值