Imagine I have a Map[String, String]
in 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:
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
?
Scala's pattern matching statement is most useful for matching on algebraic types expressed via case classes. Scala also allows the definition of patterns independently of case classes, using unapply methods in extractor objects.
By default Scala uses immutable Map.
The match method takes a number of cases as an argument. Each alternative takes a pattern and one or more expressions that will be performed if the pattern matches.
Using if expressions in case statements i match { case a if 0 to 9 contains a => println("0-9 range: " + a) case b if 10 to 19 contains b => println("10-19 range: " + a) case c if 20 to 29 contains c => println("20-29 range: " + a) case _ => println("Hmmm...") }
You could use flatMap
to pull out the values you are interested in and then match against them:
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.
You can check whether an arbitrary list of keys have particular values like 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 map
instead of flatMap
and be explicit with Some
/None
in 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.
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.
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 None
rather than throwing an exception if the key doesn't exist in the map.
Pattern matching is not what you want. You want to find if A fully contains 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)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With