2012-11-23 109 views
16

想象一下,我在斯卡拉有一個Map[String, String]與斯卡拉的模式匹配地圖類型

我想匹配地圖中的全套鍵值對。

像這樣的東西應該是可能的

val record = Map("amenity" -> "restaurant", "cuisine" -> "chinese", "name" -> "Golden Palace") 
record match { 
    case Map("amenity" -> "restaurant", "cuisine" -> "chinese") => "a Chinese restaurant" 
    case Map("amenity" -> "restaurant", "cuisine" -> "italian") => "an Italian restaurant" 
    case Map("amenity" -> "restaurant") => "some other restaurant" 
    case _ => "something else entirely" 
} 

編譯器會抱怨thulsy:

error: value Map is not a case class constructor, nor does it have an unapply/unapplySeq method

目前什麼是對一個Map鍵值組合模式匹配的最佳方法是什麼?

回答

2

模式匹配不是你想要的。你想找到,如果一個完全含有B

val record = Map("amenity" -> "restaurant", "cuisine" -> "chinese", "name" -> "Golden Palace") 
val expect = Map("amenity" -> "restaurant", "cuisine" -> "chinese") 
expect.keys.forall(key => expect(key) == record(key)) 

編輯:添加匹配標準

這種方式,您可以添加匹配標準容易

val record = Map("amenity" -> "restaurant", "cuisine" -> "chinese", "name" -> "Golden Palace") 

case class FoodMatcher(kv: Map[String,String], output: String) 

val matchers = List( 
    FoodMatcher( Map("amenity" -> "restaurant", "cuisine" -> "chinese"), "chinese restaurant, che che"), 
    FoodMatcher( Map("amenity" -> "restaurant", "cuisine" -> "italian"), "italian restaurant, mama mia") 
) 

for { 
    matcher <- matchers if matcher.kv.keys.forall(key => matcher.kv(key) == record(key)) 
} yield matcher.output 

給出:

List(chinese restaurant, che che)

6

你可以只查找值的問題,在一個元組他們堅持,並在該模式匹配:

val record = Map("amenity" -> "restaurant", "cuisine" -> "chinese", "name" -> "Golden Palace") 
(record.get("amenity"), record.get("cuisine")) match { 
    case (Some("restaurant"), Some("chinese")) => "a Chinese restaurant" 
    case (Some("restaurant"), Some("italian")) => "an Italian restaurant" 
    case (Some("restaurant"), _) => "some other restaurant" 
    case _ => "something else entirely" 
} 

或者,你可以做一些嵌套的比賽中,這可能是有點清潔:

val record = Map("amenity" -> "restaurant", "cuisine" -> "chinese", "name" -> "Golden Palace") 
record.get("amenity") match { 
    case Some("restaurant") => record.get("cuisine") match { 
    case Some("chinese") => "a Chinese restaurant" 
    case Some("italian") => "an Italian restaurant" 
    case _ => "some other restaurant" 
    } 
    case _ => "something else entirely" 
} 

注意map.get(key)返回Option[ValueType](在這種情況下,值類型是字符串),所以它會返回None而不是拋出一個異常,如果該鍵不存在於地圖存在。

+0

嵌套匹配的解決方案看起來相當不錯。 –

8

您可以使用flatMap拉出你的價值觀興趣,然後對陣他們:

List("amenity","cuisine") flatMap (record get _) match { 
    case "restaurant"::"chinese"::_ => "a Chinese restaurant" 
    case "restaurant"::"italian"::_ => "an Italian restaurant" 
    case "restaurant"::_   => "some other restaurant" 
    case _       => "something else entirely" 
} 

見#1 this snippets page

你可以查看是否鍵的任意列表有特別就像這樣:

if ((keys flatMap (record get _)) == values) ... 

注意的是,以上的工程,即使鍵可以從地圖上不存在,但如果鍵分享有些值可能需要使用map而不是flatMap,並在值列表中顯式使用Some/None。例如。在這種情況下,如果「舒適」可能不存在,「美食」的價值可能是「餐廳」(對於這個例子很愚蠢,但可能不在另一個環境中),那麼case "restaurant"::_就不明確。

另外,值得注意的是case "restaurant"::"chinese"::_case List("restaurant","chinese")略高效,因爲後者不必要地檢查在這兩者之後沒有更多元素。

+0

就像道文的回答一樣,你不能拿任意值來匹配。 –

+0

我不明白,紀堯姆 - 你能詳細解釋一下嗎? – AmigoNico

+0

看看我的編輯 –

2

我發現下面的解決方案使用與案例類最相似的提取器。這主要是句法肉汁。

object Ex { 
    def unapply(m: Map[String, Int]) : Option[(Int,Int) = for { 
     a <- m.get("A") 
     b <- m.get("B") 
    } yield (a, b) 
} 

val ms = List(Map("A" -> 1, "B" -> 2), 
    Map("C" -> 1), 
    Map("C" -> 1, "A" -> 2, "B" -> 3), 
    Map("C" -> 1, "A" -> 1, "B" -> 2) 
    ) 

ms.map { 
    case Ex(1, 2) => println("match") 
    case _  => println("nomatch") 
} 
1

因爲儘管一致認爲,所有其他的答案都非常懂事,我很感興趣,看看是否有其實用地圖的方式來模式匹配,我整理出以下。它使用與頂部答案相同的邏輯來確定匹配。

class MapSubsetMatcher[Key, Value](matcher: Map[Key, Value]) { 
    def unapply(arg: Map[Key, Value]): Option[Map[Key, Value]] = { 
    if (matcher.keys.forall(
     key => arg.contains(key) && matcher(key) == arg(key) 
    )) 
     Some(arg) 
    else 
     None 
    } 
} 

val chineseRestaurant = new MapSubsetMatcher(Map("amenity" -> "restaurant", "cuisine" -> "chinese")) 
val italianRestaurant = new MapSubsetMatcher(Map("amenity" -> "restaurant", "cuisine" -> "italian")) 
val greatPizza = new MapSubsetMatcher(Map("pizza_rating" -> "excellent")) 

val record = Map("amenity" -> "restaurant", "cuisine" -> "chinese", "name" -> "Golden Palace") 
val frankies = Map("amenity" -> "restaurant", "cuisine" -> "italian", "name" -> "Frankie's", "pizza_rating" -> "excellent") 


def matcher(x: Any): String = x match { 
    case greatPizza(_) => "It's really good, you should go there." 
    case chineseRestaurant(matchedMap) => "a Chinese restaurant called " + 
    matchedMap.getOrElse("name", "INSERT NAME HERE") 
    case italianRestaurant(_) => "an Italian restaurant" 
    case _ => "something else entirely" 
} 

matcher(record) 
// a Chinese restaurant called Golden Palace 
matcher(frankies) 
// It's really good, you should go there. 
0

,這就需要你指定要提取的按鍵,並允許您以匹配值的另一種版本是:

class MapIncluding[K](ks: K*) { 
    def unapplySeq[V](m: Map[K, V]): Option[Seq[V]] = if (ks.forall(m.contains)) Some(ks.map(m)) else None 
} 

val MapIncludingABC = new MapIncluding("a", "b", "c") 
val MapIncludingAAndB = new MapIncluding("a", "b") 

Map("a" -> 1, "b" -> 2) match { 
    case MapIncludingABC(a, b, c) => println("Should not happen") 
    case MapIncludingAAndB(1, b) => println(s"Value of b inside map is $b") 
}