【问题标题】:Pattern matching against Scala Map type针对 Scala Map 类型的模式匹配
【发布时间】:2012-11-23 22:56:06
【问题描述】:

假设我在 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"
}

编译器抱怨thulsy:

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

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

【问题讨论】:

    标签: scala map pattern-matching


    【解决方案1】:

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

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

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

    请注意,即使地图中可能不存在键,上述方法仍然有效,但如果键共享某些值,您可能希望使用 map 而不是 flatMap 并明确使用 Some/None in你的价值观清单。例如。在这种情况下,如果“amenity”可能不存在并且“cuisine”的值可能是“restaurant”(这个例子很愚蠢,但在其他情况下可能不是),那么case "restaurant"::_ 将是模棱两可的。

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

    【讨论】:

    • 就像道文的回答一样,你不能取任意值来匹配。
    • 我不明白,纪尧姆 -- 你能详细说明一下吗?
    • 好的,我想我明白你在说什么。假设 PartialFunction 是一个参数而不是内联代码,并且您事先不知道它依赖于哪些键。然后你就不会知道 flatMap 了。在这种情况下,您不会使用 PartialFunctions;您可以将keysvalues 作为参数,然后像上面一样执行if
    【解决方案2】:

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

    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 而不是抛出异常。

    【讨论】:

      【解决方案3】:

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

      【讨论】:

        【解决方案4】:

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

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

        【讨论】:

          【解决方案5】:

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

          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.
          

          【讨论】:

            【解决方案6】:

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

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

            【讨论】:

              猜你喜欢
              • 2013-09-06
              • 2014-09-26
              • 2014-09-30
              • 2011-11-27
              • 2018-04-12
              • 2015-10-06
              • 2020-08-09
              • 2013-03-17
              • 2014-03-05
              相关资源
              最近更新 更多