2012-11-23 12 views
16

Wyobraź sobie, że mam Map[String, String] w Scali.Dopasowanie wzorców do Scala Mapa typu

Chcę dopasować do pełnego zestawu par klucz-wartość na mapie.

Coś takiego powinno być możliwe

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

Kompilator narzeka thulsy:

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

Co obecnie jest najlepszym sposobem na wzór meczu o kombinacji klucz-wartość w Map?

Odpowiedz

2

Dopasowywanie wzorców nie jest tym, czego potrzebujesz. Chcesz się dowiedzieć, czy w pełni zawiera 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: dodanie kryteriów dopasowywania

ten sposób można dodać pasujących kryteriów łatwo

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 

Daje:

List(chinese restaurant, che che)

6

Można tylko patrzeć wartości w pytaniu, trzymać je w krotce, a wzór mecz na tym:

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

Albo można zrobić kilka zagnieżdżonych mecze, co może być nieco czystsze:

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

Zauważ, że map.get(key) zwraca Option[ValueType] (w tym przypadku ValueType byłoby String), więc będzie wrócić None zamiast rzucać wyjątek, jeśli klucz nie istnieje na mapie.

+0

zagnieżdżona rozwiązanie dopasowanie wygląda całkiem dobrze. –

8

Możesz użyć flatMap, aby wyciągnąć Wartości są Państwo zainteresowani, a następnie dopasować przeciwko nim:

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

Zobacz # 1 this snippets page.

Można sprawdzić, czy dowolna lista kluczy mają szczególne wartości tak:

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

Zauważ, że powyższy działa nawet jeśli klucze mogą być nieobecny z mapy, ale jeśli udziału kluczy niektóre wartości prawdopodobnie używają wartości zamiast flatMap i na liście wartości muszą być jednoznaczne z Some/None. Na przykład. w tym przypadku, jeśli "udogodnienie" może być nieobecne, a wartość "kuchni" może być "restauracja" (głupie dla tego przykładu, ale być może nie w innym kontekście), wówczas case "restaurant"::_ byłoby niejednoznaczne.

Warto również zauważyć, że case "restaurant"::"chinese"::_ jest nieco bardziej wydajny niż case List("restaurant","chinese"), ponieważ ten drugi niepotrzebnie sprawdza, czy po tych dwóch elementach nie ma już elementów.

+0

, podobnie jak odpowiedź od DaoWen, nie można wziąć arbitralnej wartości, aby dopasować. –

+0

Nie rozumiem, Guillaume - czy możesz rozwinąć? – AmigoNico

+0

spójrz na moją edycję –

2

Znajduję następujące rozwiązanie używając ekstraktorów najbardziej podobnych do klas przypadków. Jest to jednak głównie sos syntaktyczny.

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

Bo mimo zgadzając się, że wszystkie inne odpowiedzi są bardzo rozsądne, jestem zainteresowany, aby zobaczyć, czy nie było w rzeczywistości sposobem na wzór-meczu za pomocą mapy, ułożyła, co następuje. Używa tej samej logiki, co najlepsza odpowiedź, aby ustalić dopasowanie.

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

Inna wersja, która wymaga, aby określić klucze chcesz wyodrębnić i pozwala dopasować się na wartościach jest następujący:

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