Scala meilleure façon de transformer une Collection en une carte par clé?
Si j'ai une collection c
de type T
et il existe une propriété p
sur T
(de type P
, par exemple), quel est le meilleur moyen de faire un carte-par extraction-clé?
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'un mutable carte. Y a-t-il une meilleure façon de le faire 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 que dans Scala il n'y a pas besoin)
11 réponses
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. Utilisez donc 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", "string", "to", "length") map {s => (s, s.length)}
list: List[(java.lang.String, Int)] = List((this,4), (maps,4), (string,6), (to,2), (length,6))
scala> val list = List("this", "is", "a", "bunch", "of", "strings")
list: List[java.lang.String] = List(this, is, a, bunch, of, strings)
scala> val string2Length = Map(list map {s => (s, s.length)} : _*)
string2Length: scala.collection.immutable.Map[java.lang.String,Int] = Map(strings -> 7, of -> 2, bunch -> 5, a -> 1, is -> 2, this -> 4)
En plus de la solution de @James Iry, il est également possible d'accomplir cela en utilisant un pli. Je soupçonne que cette solution est légèrement plus rapide que la méthode tuple (moins d'objets garbage sont créés):
val list = List("this", "maps", "string", "to", "length")
val map = list.foldLeft(Map[String, Int]()) { (m, s) => m(s) = s.length }
Cela peut être mis en œuvre de manière immuable et avec une seule traversée en pliant à travers 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 à travers l'opération de pliage.
Le compromis ici est la simplicité du code par rapport à son efficacité. Ainsi, pour les grandes collections, cette approche peut être plus appropriée que d'utiliser 2 implémentations traversales telles que comme application 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 des intermédiaires, plus d'infos ici: Scala 2.8 breakOut
Ce que vous essayez de réaliser est un peu indéfini.
Si deux ou plusieurs éléments dans c
partagent le même p
? Quel élément sera mappé à ce p
dans la carte?
La façon la plus précise de regarder cela donne une carte entre p
et tous les c
éléments qui l'ont:
val m: Map[P, Collection[T]]
Cela peut être facilement réalisé avec groupBy:
val m: Map[P, Collection[T]] = c.groupBy(t => t.p)
Si vous voulez toujours la carte d'origine, vous pouvez, par exemple, mapper p
au premier t
qui a il:
val m: Map[P, T] = c.groupBy(t => t.p) map { case (p, ts) => p -> ts.head }
c map (_.getP) zip c
Fonctionne bien et est très intuitiv
Pour ce que ça vaut, voici deuxinutiles façons 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 appelant plus lisible. J'ai utilisé des conversions implicites pour ajouter 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 D'appel:
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 appelant doit importer les implicitConversions 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 de toMap
val map = list.map(e => (e, e.length)).toMap