针对 Scala Map 类型的模式匹配

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/13536619/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-22 04:44:29  来源:igfitidea点击:

Pattern matching against Scala Map type

scalamappattern-matching

提问by Tom Morris

Imagine I have a Map[String, String]in Scala.

想象一下我Map[String, String]在 Scala 中有一个。

I want to match against the full set of key–value pairings in the map.

我想与地图中的全套键值对进行匹配。

Something like this ought to be possible

这样的事情应该是可能的

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"
}

The compiler complains thulsy:

编译器抱怨 thulsy:

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

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

What currently is the best way to pattern match for key–value combinations in a Map?

目前对 a 中的键值组合进行模式匹配的最佳方法是Map什么?

采纳答案by Guillaume Massé

Pattern matching is not what you want. You want to find if A fully contains B

模式匹配不是您想要的。您想查找 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 ) )

Edit: adding matching criteria

编辑:添加匹配条件

This way you can add matching criteria easily

这样您就可以轻松添加匹配条件

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

Gives:

给出:

List(chinese restaurant, che che)

List(chinese restaurant, che che)

回答by AmigoNico

You could use flatMapto pull out the values you are interested in and then match against them:

您可以使用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"
}

See #1 on this snippets page.

请参阅此代码段页面上的 #1 。

You can check whether an arbitrary list of keyshave particular valueslike so:

您可以检查任意列表是否具有特定值,如下所示:

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

Note that the above works even if keys can be absent from the map, but if the keys share some values you probably want to use mapinstead of flatMapand be explicit with Some/Nonein your list of values. E.g. in this case if "amenity" might be absent and the value of "cuisine" might be "restaurant" (silly for this example, but perhaps not in another context), then case "restaurant"::_would be ambiguous.

请注意,即使映射中可能没有键,上述方法也能正常工作,但如果键共享某些值,您可能希望使用这些值map而不是在值列表中flatMap使用Some/显式None。例如,在这种情况下,如果“amenity”可能不存在并且“cuisine”的价值可能是“restaurant”(对于这个例子来说很愚蠢,但可能不是在另一个上下文中),那么case "restaurant"::_将是模棱两可的。

Also, it is worth noting that case "restaurant"::"chinese"::_is slightly more efficient than case List("restaurant","chinese")because the latter needlessly checks that there are no more elements after those two.

此外,值得注意的case "restaurant"::"chinese"::_是,这比case List("restaurant","chinese")因为后者不必要地检查在这两个之后没有更多元素而略有效率。

回答by DaoWen

You could just look up the values in question, stick them in a tuple, and pattern match on that:

你可以只查找有问题的值,把它们放在一个元组中,然后进行模式匹配:

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"
}

Or, you could do some nested matches, which might be a bit cleaner:

或者,您可以进行一些嵌套匹配,这可能会更清晰一些:

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"
}

Note that map.get(key)returns an Option[ValueType](in this case ValueType would be String), so it will return Nonerather than throwing an exception if the key doesn't exist in the map.

请注意,map.get(key)返回一个Option[ValueType](在这种情况下 ValueType 将是 String),因此None如果该键在地图中不存在,它将返回而不是抛出异常。

回答by Joerg Schmuecker

I find the following solution using extractors the most similar to case classes. It's mostly syntactic gravy though.

我发现以下使用与案例类最相似的提取器的解决方案。不过,它主要是句法肉汁。

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")
}

回答by Nightscape

Another version which requires you to specify the keys you want to extract and allows you to match on the values is the following:

另一个要求您指定要提取的键并允许您匹配值的版本如下:

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")
}

回答by Pete Wildsmith

Because, despite agreeing that all the other answers are very sensible, I was interested to see if there was in fact a way to pattern-match using maps, I put together the following. It uses the same logic as the top answer to determine a match.

因为,尽管我同意所有其他答案都非常明智,但我很想知道实际上是否有一种使用地图进行模式匹配的方法,所以我整理了以下内容。它使用与最佳答案相同的逻辑来确定匹配。

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.