想象一下,我在Scala中有一个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"
}
编译器抱怨说:
error: value Map is not a case class constructor, nor does it have an unapply/unapplySeq method
目前哪种模式匹配Map
答案 0 :(得分:11)
您可以使用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"
}
请参阅this snippets page上的#1。
您可以检查键的任意列表是否具有特定的值,如下所示:
if ( ( keys flatMap ( record get _ ) ) == values ) ...
请注意,即使地图上没有关键字,上述方法仍然有效,但如果关键字共享某些值,您可能希望使用map
代替flatMap
,并明确Some
您的值列表中的/ None
。例如。在这种情况下,如果“美食”可能不存在且“美食”的价值可能是“餐馆”(这个例子很愚蠢,但也许不在另一个上下文中),那么case "restaurant"::_
将是模棱两可的。
另外,值得注意的是case "restaurant"::"chinese"::_
比case List("restaurant","chinese")
略高一些,因为后者不必要地检查在这两者之后没有更多的元素。
答案 1 :(得分:7)
你可以查看有问题的值,将它们粘贴在一个元组中,并在其上进行模式匹配:
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]
(在这种情况下,ValueType将是String),因此它将返回None
而不是如果该键中不存在该键则抛出异常地图。
答案 2 :(得分:5)
模式匹配不是你想要的。您想要查找A是否完全包含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)
答案 3 :(得分:2)
我发现以下解决方案使用与case类最相似的提取器。它虽然主要是语法上的肉汁。
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")
}
答案 4 :(得分:2)
另一个需要您指定要提取的键并允许您匹配值的版本如下:
SIZE
答案 5 :(得分: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.