Scala meilleur moyen de transformer une collection en une carte par clé?

Si j’ai une collection c de type T et qu’il existe une propriété p sur T (de type P , par exemple), quelle est la meilleure façon de faire une carte par extraction ?

 val c: Collection[T] val m: Map[P, T] 

Une façon est la suivante:

 m = new HashMap[P, T] c foreach { t => m add (t.getP, t) } 

Mais maintenant, j’ai besoin d’une carte mutable . Y a-t-il une meilleure façon de faire cela pour qu’il soit en 1 ligne et que je me retrouve avec une carte immuable ? (Évidemment, je pourrais transformer ce qui précède en un simple utilitaire de bibliothèque, comme je le ferais en Java, mais je soupçonne qu’à Scala, il n’est pas nécessaire)

    Vous pouvez utiliser

     c map (t => t.getP -> t) toMap 

    mais sachez que cela nécessite 2 traversées.

    Vous pouvez construire une carte avec un nombre variable de tuples. Donc, utilisez la méthode map sur la collection pour la convertir en une collection de tuples, puis utilisez l’astuce: _ * pour convertir le résultat en un argument variable.

     scala> val list = List("this", "maps", "ssortingng", "to", "length") map {s => (s, s.length)} list: List[(java.lang.Ssortingng, Int)] = List((this,4), (maps,4), (ssortingng,6), (to,2), (length,6)) scala> val list = List("this", "is", "a", "bunch", "of", "ssortingngs") list: List[java.lang.Ssortingng] = List(this, is, a, bunch, of, ssortingngs) scala> val ssortingng2Length = Map(list map {s => (s, s.length)} : _*) ssortingng2Length: scala.collection.immutable.Map[java.lang.Ssortingng,Int] = Map(ssortingngs -> 7, of -> 2, bunch -> 5, a -> 1, is -> 2, this -> 4) 

    En plus de la solution de @James Iry, il est également possible d’y parvenir en utilisant un pli. Je soupçonne que cette solution est légèrement plus rapide que la méthode tuple (moins d’objects indésirables sont créés):

     val list = List("this", "maps", "ssortingng", "to", "length") val map = list.foldLeft(Map[Ssortingng, Int]()) { (m, s) => m(s) = s.length } 

    Ceci peut être implémenté immuablement et avec une seule traversée en pliant la collection comme suit.

     val map = c.foldLeft(Map[P, T]()) { (m, t) => m + (t.getP -> t) } 

    La solution fonctionne car l’ajout à une carte immuable renvoie une nouvelle carte immuable avec l’entrée supplémentaire et cette valeur sert d’accumulateur pendant l’opération de pliage.

    Le compromis est la simplicité du code par rapport à son efficacité. Donc, pour les grandes collections, cette approche peut être plus appropriée que d’utiliser deux implémentations de traversées telles que l’application de la map et toMap .

    Une autre solution (peut ne pas fonctionner pour tous les types)

     import scala.collection.breakOut val m:Map[P, T] = c.map(t => (t.getP, t))(breakOut) 

    cela évite la création de la liste intermédiaire, plus d’infos ici: Scala 2.8 breakOut

    Ce que vous essayez de réaliser est un peu indéfini.
    Que se passe-t-il si deux ou plusieurs éléments de c partagent le même p ? Quel object sera mappé sur celui-ci sur la carte?

    La manière la plus précise de regarder ceci est de fournir une carte entre p et tous les objects c qui l’ont:

     val m: Map[P, Collection[T]] 

    Cela pourrait être facilement réalisé avec groupBy :

     val m: Map[P, Collection[T]] = c.groupBy(t => tp) 

    Si vous voulez toujours la carte originale, vous pouvez, par exemple, mapper p au premier t qui la contient:

     val m: Map[P, T] = c.groupBy(t => tp) map { case (p, ts) => p -> ts.head } 
     c map (_.getP) zip c 

    Fonctionne bien et est très intuitif

    Pour ce que cela vaut, voici deux manières inutiles de le faire:

     scala> case class Foo(bar: Int) defined class Foo scala> import scalaz._, Scalaz._ import scalaz._ import Scalaz._ scala> val c = Vector(Foo(9), Foo(11)) c: scala.collection.immutable.Vector[Foo] = Vector(Foo(9), Foo(11)) scala> c.map(((_: Foo).bar) &&& identity).toMap res30: scala.collection.immutable.Map[Int,Foo] = Map(9 -> Foo(9), 11 -> Foo(11)) scala> c.map(((_: Foo).bar) >>= (Pair.apply[Int, Foo] _).curried).toMap res31: scala.collection.immutable.Map[Int,Foo] = Map(9 -> Foo(9), 11 -> Foo(11)) 

    Ce n’est probablement pas le moyen le plus efficace de transformer une liste en carte, mais cela rend le code d’appel plus lisible. J’ai utilisé des conversions implicites pour append une méthode mapBy à la liste:

     implicit def list2ListWithMapBy[T](list: List[T]): ListWithMapBy[T] = { new ListWithMapBy(list) } class ListWithMapBy[V](list: List[V]){ def mapBy[K](keyFunc: V => K) = { list.map(a => keyFunc(a) -> a).toMap } } 

    Exemple de code appelant:

     val list = List("A", "AA", "AAA") list.mapBy(_.length) //Map(1 -> A, 2 -> AA, 3 -> AAA) 

    Notez qu’en raison de la conversion implicite, le code de l’appelant doit importer les implicConversions de scala.

    Cela fonctionne pour moi:

     val personsMap = persons.foldLeft(scala.collection.mutable.Map[Int, PersonDTO]()) { (m, p) => m(p.id) = p; m } 

    La carte doit être mutable et la carte doit être retournée car l’ajout à une carte mutable ne renvoie pas de carte.

    utilisez map () sur la collection suivie avec toMap

     val map = list.map(e => (e, e.length)).toMap