16-提取器

1. 示例:提取電子郵件地址

定義了unapply(提取)函數的對象就可以稱爲提取器,而apply(注入)不是必須的。

object EMail {
  // 注入(可選)
  def apply(user: String, domain: String) = user + "@" + domain
  // 提取(必須)
  def unapply(str: String): Option[(String, String)] = {
    val parts = str split "@"
    if (parts.length == 2) Some(parts(0), parts(1)) else None
  }
}
val EMail(user, domain) = "[email protected]"
println("user: " + user +", domain: " + domain)

運行結果:
在這裏插入圖片描述
同時,因爲定義了apply,可以如下操作:

// 運行:
println(EMail("guyt", "qq.com"))

// 結果:
[email protected]

2. 提取0或1個變量的模式

EMail對象的unapply方法在成功的情況下會返回一對元素值,這很容易想到是否能返回多個元素值?
爲了返回單個模式元素,unappl這是簡單的將結果放入Some:

object Twice {
  def apply(s: String): String = s + s
  // 如果是連續重複的則返回一半,如DIDI 返回 DI
  def unapply(s: String): Option[String] = {
    val length = s.length / 2
    val half = s.substring(0, length)
    if (half == s.substring(length)) Some(half) else None
  }
}

也可以定義提取器模式並不綁定任何變量,unapply方法返回布爾值,如:

object UpperCase {
  // s.toUpperCase == s 則返回true
  def unapply(s: String): Boolean = s.toUpperCase == s
}

這裏使用一個userTwiceUpper函數,在它的模式匹配中選擇應用上面提到的所有提取器:

def userTwiceUpper(s: String) = s match {
  case EMail(Twice(user @ UpperCase()), domain) => "match: " + user + " in domain " + domain
  case _ => "no match"
}

運行測試:

// 運行:
println(userTwiceUpper("[email protected]"))
println(userTwiceUpper("[email protected]"))
println(userTwiceUpper("[email protected]"))

// 結果:
match: DI in domain qq.com
no match
no match
分析:

這裏博主給出自己的分析:

  1. 首先EMail對s 應用unapply函數,若成功匹配則返回了第一個參數和第二個參數。第一個參數賦值給Twice 繼續進行匹配,第二個參數賦值給domain。
  2. 進行Twice匹配,若成功匹配則返回值,將返回的值賦值給UpperCase繼續進行匹配。
  3. 進行UpperCase的匹配,若成功則返回匹配的對象注:不是匹配的返回值,正是因爲這樣,UpperCase纔沒有返回布爾值)。user @ UpperCase() 是模式匹配中的變量綁定,這裏@可以當作是語法,如果@的右邊 匹配成功,就將匹配對象賦值給@的左邊。
xxx(省略) s match {
case EMail(Twice(user @ UpperCase()), domain) => "match: " + user + " in domain " + domain
xxx(省略)
}
完整代碼可見GitHub:14-Extractor

3. 提取可變長度參數的模式

就那匹配電子郵箱來說,你可能還想要匹配域名部分中的頂級域名,如:
於unapply不同,要想支持變長參數匹配,需要使用unapplySeq方法,其返回結果必須是Option[Seq[T]]類型

object Domain {
  // 注入
  def apply(parts: String*): String = parts.reverse.mkString(".")
  // 提取
  def unapplySeq(whole: String): Option[Seq[String]] = Some(whole.split("\\.").reverse)
}

因此,可以使用Domain提取器來獲取關於電子郵件字符串的更詳細的信息,如要查找名爲tom,後綴爲.com 的郵件地址:

// 查找名爲tom,後綴爲.com 的郵件地址
def isTomInDotCom(s: String): Boolean = s match {
  case EMail("tom", Domain("com", _*)) => true
  case _ => false
}

測試:

// test
println(isTomInDotCom("[email protected]"))
println(isTomInDotCom("[email protected]"))
println(isTomInDotCom("[email protected]"))

// 結果
true
false
false

到這裏,可以寫出一個新版本的EMail,如:

object ExpandedEMail {
  def unapplySeq(email: String): Option[(String, Seq[String])] = {
    val parts = email split "@"
    if (parts.length == 2)
      Some(parts(0), parts(1).split("\\.").reverse)
    else
      None
  }
}

val s = "[email protected]"
val ExpandedEMail(name, topDom, subdoms @ _*) = s
println(name)
println(topDom)
println(subdoms)

// 結果
tom
ch
WrappedArray(epfl, support)

4. 提取器和序列模式

例如,下面語句能正常運行,是因爲List的伴生對象繼承自SeqFactory,而SeqFactory,中定義了unapplySeq方法(如下圖):
在這裏插入圖片描述
在這裏插入圖片描述

5. 正則表達式

如:(-)?(\d+)(.\d*)? 是匹配任意整數、浮點數的
在scala需要這樣定義,注意\ .是需要轉義的:
在這裏插入圖片描述
因爲,scala中""" 包裹的字符串是原生字符串,即\ . 等是其符號本身,不需要轉義:
在這裏插入圖片描述
除此之外,scala還提供StringOps(String 與它存在隱式轉換)又一個名爲.r 的方法,根據這個方法就可以使用字符串獲得正則表達式:
在這裏插入圖片描述
測試正則:
在這裏插入圖片描述
你可以看到這裏使用了三個方法:findAllIn、findFirstIn、findPrefixOf

另外,在正則表達式中我們使用的組(即() 包裹起來的),這裏共使用了三個組,所以又如下的拆解方法:
在這裏插入圖片描述

完!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章