2017-09-14 3 views
1

Je voudrais créer une union de deux cartes dont le type clé est la même et dont le type valeur est une collection d'éléments, mais dont les types sont différents.scala: union de deux cartes dont le type de clé est la même et dont le type valeur est une collection d'éléments, mais dont les types sont différents

Prenons l'exemple suivant artificiel:

case class Child(name: String) 
val peopleToChildren: Map[String, Seq[Child]] = 
    Map("max" -> Seq(Child("a"), Child("b")), 
    "yaneeve" -> Seq(Child("y"), Child("d"))) 

case class Pet(name: String) 
val peopleToPets: Map[String, Seq[Pet]] = 
    Map("max" -> Seq(Pet("fifi")), 
    "jill" -> Seq(Pet("bobo"), Pet("jack"), Pet("Roger rabbit"))) 

val peopleToChildrenAndDogs: Map[String, (Seq[Child], Seq[Pet])] = { 
    // people may have children 
    // people may have pets 
    // would like a map from people to a tuple with a potentially empty list of children and a 
    //  potentially empty list of pets 
    // ??? 
} 

Quelle serait une façon de le faire qui est concise, idiomatiques, mais toujours lisible?

Je trouve pas une seule fonction qui peut le faire dans la bibliothèque standard de collections scala.

Les solutions proposées peuvent être basées uniquement sur la bibliothèque standard, ou proposer une solution externe.

je poste ici depuis que je ne pouvais pas trouver facilement une solution en ligne pour une opération apparemment standard.

Répondre

3

Cela semble fonctionner.

val peopleToChildrenAndDogs: Map[String, (Seq[Child], Seq[Pet])] = { 
    (peopleToChildren.keySet ++ peopleToPets.keySet).map { k => 
    k -> (peopleToChildren.getOrElse(k, Seq()) 
     ,peopleToPets.getOrElse(k, Seq())) 
    }.toMap 
} 

Obtenez toutes les clés. Pour chaque touche, faites getOrElse() sur chacune des cartes d'accès.

+0

aussi cool que la solution de @ OlegPyzhcov est, et c'est, j'ai fini par utiliser cette version, car il n'avait pas de deps externes et est concis – Yaneeve

0

Pour répondre à ma propre question, ce qui suit est la façon dont je l'ai résolu, mais il semble trop long et complexe:

Welcome to the Ammonite Repl 1.0.2 
(Scala 2.11.11 Java 1.8.0_91) 
If you like Ammonite, please support our development at www.patreon.com/lihaoyi 
@ case class Child(name: String) 
defined class Child 

@ val peopleToChildren: Map[String, Seq[Child]] = 
    Map("max" -> Seq(Child("a"), Child("b")), 
     "yaneeve" -> Seq(Child("y"), Child("d"))) 
peopleToChildren: Map[String, Seq[Child]] = Map("max" -> List(Child("a"), Child("b")), "yaneeve" -> List(Child("y"), Child("d"))) 

@ 

@ case class Pet(name: String) 
defined class Pet 

@ val peopleToPets: Map[String, Seq[Pet]] = 
    Map("max" -> Seq(Pet("fifi")), 
     "jill" -> Seq(Pet("bobo"), Pet("jack"), Pet("Roger rabbit"))) 
peopleToPets: Map[String, Seq[Pet]] = Map("max" -> List(Pet("fifi")), "jill" -> List(Pet("bobo"), Pet("jack"), Pet("Roger rabbit"))) 

@ 

@ val peopleToChildrenAndDogs: Map[String, (Seq[Child], Seq[Pet])] = { 
    // people may have children 
    // people may have pets 
    // would like a map from people to a tuple with a potentially empty list of children and a 
    //  potentially empty list of pets 

    val paddedPeopleToChildren = peopleToChildren.map{ case (person, children) => person -> (children, List.empty[Pet])} 
    val paddedPeopleToPets = peopleToPets.map{ case (person, pets) => person ->(List.empty[Child], pets)} 
    val notGoodEnough = paddedPeopleToPets ++ paddedPeopleToChildren // this is here to show that it does not work since it overwrites the value of a key - Map(max -> (List(Child(a), Child(b)),List()), jill -> (List(),List(Pet(bobo), Pet(jack), Pet(Roger rabbit))), yaneeve -> (List(Child(y), Child(d)),List())) 

    val allSeq = paddedPeopleToPets.toSeq ++ paddedPeopleToChildren.toSeq 
    val grouped = allSeq.groupBy(_._1).mapValues(_.map { case (_, tup) => tup }) 
    val solution = grouped.mapValues(_.unzip).mapValues {case (wrappedChildren, wrappedPets) => (wrappedChildren.flatten, wrappedPets.flatten)} 
    solution 
    } 
peopleToChildrenAndDogs: Map[String, (Seq[Child], Seq[Pet])] = Map(
    "yaneeve" -> (ArrayBuffer(Child("y"), Child("d")), ArrayBuffer()), 
    "max" -> (ArrayBuffer(Child("a"), Child("b")), ArrayBuffer(Pet("fifi"))), 
    "jill" -> (ArrayBuffer(), ArrayBuffer(Pet("bobo"), Pet("jack"), Pet("Roger rabbit"))) 
) 
3

Juste pour les curieux, voici comment cela pourrait se faire à l'aide Scalaz:

import scalaz._, Scalaz._ 

case class Child(name: String) 

val peopleToChildren = Map(
    "max"  -> List(Child("a"), Child("b")), 
    "yaneeve" -> List(Child("y"), Child("d")) 
) 

case class Pet(name: String) 

val peopleToPets = Map(
    "max" -> List(Pet("fifi")), 
    "jill" -> List(Pet("bobo"), Pet("jack"), Pet("Roger rabbit")) 
) 

val peopleToChildrenAndPets: Map[String, (List[Child], List[Pet])] = 
    peopleToChildren.strengthR(nil[Pet]) |+| peopleToPets.strengthL(nil[Child]) 

Explication:

  • nil[Pet] est juste un alias pour List.empty[Pet]
  • strengthR pour une donnée Functor tuples contenait valeurs, de sorte que son paramètre est à droite. Ici, il est équivalent à peopleToChildren.mapValues(v => (v, nil[Pet]))
  • strengthL est le même, mais l'élément sera ajouté à la
  • gauche
  • |+| est un opérateur append pour un Semigroup donné. Celui ici est dérivé récursive:
    • pour Map[K, V], il utilise |+| de combiner les valeurs de type V si une donnée clé existe dans les deux cartes. Si la valeur n'est présente que dans l'un d'entre eux, elle sera conservée telle quelle. Ici, V = (List[Child], List[Pet])
    • pour les tuples (A, B), il utilise à nouveau |+| pour combiner à la fois A s et B s. Ici, A = List[Child] et B = List[Pet]
    • pour les listes de tout type (ainsi que des chaînes, des vecteurs ou des cours d'eau), il fait concaténation.Voilà pourquoi je devais changer le type de valeurs de la carte pour être List s - pour Seq générique de cette opération n'est pas définie

Résultat:

peopleToChildrenAndPets: Map[String, (List[Child], List[Pet])] = Map(
    "max" -> (List(Child("a"), Child("b")), List(Pet("fifi"))), 
    "jill" -> (
    List(), 
    List(Pet("bobo"), Pet("jack"), Pet("Roger rabbit")) 
), 
    "yaneeve" -> (List(Child("y"), Child("d")), List()) 
) 
+0

Vraiment cool! Merci pour l'explication en profondeur! Je me demande, aussi, comment cela se ferait avec les chats semble qu'il devrait y avoir une transformation directe pour ce code – Yaneeve

+0

@Yaneeve 'strengthL' est' tupleLeft', 'strengthR' est' tupleRight', 'nil [...]' ne n'existe pas, alors utilisez 'List.empty [...]' ou 'List [...]()' et les imports sont 'import cats._, implicits._'. Peut être cassé sur 1.0.0-MF tho, alors essayez 0.9.0 si des problèmes surgissent. –

+0

fera, merci! – Yaneeve