序列提取

2018-02-24 16:00 更新

上一章講述了如何實現(xiàn)自定義的提取器以及如何在模式匹配中使用它們,但是只討論了如何從給定的數(shù)據(jù)結構中分解固定數(shù)目的參數(shù)。對某種數(shù)據(jù)結構來說,Scala 提供了提取任意多個參數(shù)的模式匹配方法。

比如,你可以匹配只有兩個、或者只有三個元素的列表:

val xs = 3 :: 6 :: 12 :: Nil
xs match {
 case List(a, b) => a * b
 case List(a, b, c) => a + b + c
 case _ => 0
}

除此之外,也可以使用通配符 _* 匹配長度不確定的列表:

val xs = 3 :: 6 :: 12 :: 24 :: Nil
xs match {
 case List(a, b, _*) => a * b
 case _ => 0
}

這個例子中,第一個模式成功匹配,把 xs 的前兩個元素分別綁定到 a 、b ,而剩余的列表,無論其還有多少個元素,都直接被忽略掉。

顯然,這種模式的提取器是無法通過上一章介紹的方法來實現(xiàn)的。需要一種特殊的方法,來使得一個提取器可以接受某一類型的對象,將其解構成列表,且這個列表的長度在編譯期是不確定的。

unapplySeq 就是用來做這件事情的,下面的代碼是其可能的方法簽名:

 def unapplySeq(object: S): Option[Seq[T]]

這個方法接受類型 S 的對象,返回一個類型參數(shù)為 Seq[T]Option 。

例子:提取給定的名字

現(xiàn)在我們舉一個例子來展示如何使用這種提取器。

假設有一個應用,其某處代碼接收了一個表示人名且類型為 String 的參數(shù),這個字符串可能包含了這個人的第二個甚至是第三個名字(如果這個人不止有一個名字)。比如說, Daniel 、 Catherina Johanna 、 Matthew John Michael 。而我們想做的是,從這個字符串中提取出單個的名字。

下面的代碼是一個用 unapplySeq 方法實現(xiàn)的提取器:

object GivenNames {
  def unapplySeq(name: String): Option[Seq[String]] = {
    val names = name.trim.split(" ")
    if (name.forall(_.isEmpty)) None
    else Some(names)
  }
}

給定一個含有一個或多個名字的字符串,這個提取器會將其解構成一個列表。如果字符串不包含有任何名字,提取器會返回 None ,提取器所在的那個模式就匹配失敗。

下面對提取器進行測試:

  def greetWithFirstName(name: String) = name match {
    case GivenNames(firstName, _*) => "Good morning, $firstname!"
    case _ => "Welcome! Please make sure to fill in your name!"
  }

greetWithFirstName("Daniel") 會返回 "Good morning, Daniel!",而 greetWithFirstName("Catherina Johanna") 會返回 "Good morning, Catherina!"。

固定和可變的參數(shù)提取

有些時候,需要提取出至少多少個值,這樣,在編譯期,就知道必須要提取出幾個值出來,再外加一個可選的序列,用來保存不確定的那一部分。

在我們的例子中,假設輸入的字符串包含了一個人完整的姓名,而不僅僅是名字。比如字符串可能是"John Doe"、"Catherina Johanna Peterson",其中,"Doe"、"Peterson"是姓,"John"、"Catherina"、"Johanna"是名。我們想做的是匹配這樣的字符串,把姓綁定到第一個變量,把第一個名字綁定到第二個變量,第三個變量存放剩下的任意個名字。

稍微修改 unapplySeq 方法就可以解決上述問題:

def unapplySeq(object: S): Option[(T1, .., Tn-1, Seq[T])]

unapplySeq 返回的同樣是 Option[TupleN] ,只不過,其最后一個元素是一個 Seq[T] 。這個方法簽名看起來應該很熟悉,它和之前的一個 unapply 簽名類似。

下列代碼是利用這個方法生成的提取器:

object Names {
  def unapplySeq(name: String): Option[(String, String, Seq[String])] = {
    val names = name.trim.split(" ")
    if (names.size < 2) None
    else Some((names.last, names.head, names.drop(1).dropRight(1)))
  }
}

仔細看看其返回值,及其構造 Some 的方式。代碼返回一個類型參數(shù)為 Tuple3Option ,這個元組包含了姓、名、以及由剩余的名字構成的序列。

如果這個提取器用在一個模式中,那只有當給定的字符串至少含有姓和名時,模式才匹配成功。

下面用這個提取器重寫 greeting 方法:

def greet(fullName: String) = fullName match {
  case Names(lastName, firstName, _*) =>
    "Good morning, $firstName $lastName!"
  case _ =>
    "Welcome! Please make sure to fill in your name!"
}

你可以在 REPL 中或者 worksheet 上試試這些代碼。

小結

這一章里,我們學會了怎樣去實現(xiàn)和使用返回不定長度值序列的提取器。提取器是一個相當強大的工具,你可以靈活的重用它們,從而提供一種有效的方法來擴展要匹配的模式。

下一章,我會給出模式匹配在 Scala 中的不同用法(現(xiàn)在所見到的只是冰山一角)。

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號