针对ScalaMap类型的模式匹配

zzlelutf  于 2022-11-09  发布在  Scala
关注(0)|答案(6)|浏览(143)

假设我在Scala中有一个Map[String, String]
我希望与Map中的全套键-值对进行匹配。
像这样的事情应该是可能的

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中的键-值组合进行模式匹配的最佳方式是什么?

dnph8jn4

dnph8jn41#

您可以使用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中没有键,上述方法也有效,但如果键共享某些值,您可能希望使用map而不是flatMap,并在值列表中显式使用Some/None。例如,在这种情况下,如果“便利”可能不存在,而“菜肴”的值可能是“餐馆”(在这个例子中是愚蠢的,但在另一个上下文中可能不是),那么case "restaurant"::_将是不明确的。
此外,值得注意的是,case "restaurant"::"chinese"::_case List("restaurant","chinese")的效率略高,因为后者不必要地检查在这两个元素之后没有更多的元素。

2o7dmzc5

2o7dmzc52#

您只需查找有问题的值,将它们放在一个元组中,然后对其进行模式匹配:

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将为字符串),因此它将返回None,而不是在Map中不存在该键时引发异常。

e0bqpujr

e0bqpujr3#

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

ct2axkht

ct2axkht4#

我发现以下使用提取程序的解决方案最类似于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")
}
esbemjvw

esbemjvw5#

因为,尽管我同意所有其他答案都是非常合理的,但我很感兴趣地想知道是否真的有一种方法可以使用Map进行模式匹配,我收集了以下内容。它使用与顶部答案相同的逻辑来确定匹配。

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.
nwwlzxa7

nwwlzxa76#

另一个版本要求您指定要提取的密钥,并允许您匹配这些值,如下所示:

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

相关问题