178 votes

La meilleure façon en Scala de transformer une Collection en une Map-by-key ?

Si j'ai une collection c de type T et il y a une propriété p sur T (de type P ), quelle est la meilleure manière de faire un carte par clé d'extraction ?

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

L'un des moyens est le suivant :

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

Mais maintenant j'ai besoin d'un mutable carte. Existe-t-il une meilleure façon de procéder pour que tout se passe sur une seule ligne et que je me retrouve avec une carte de type immuable Carte ? (Je pourrais évidemment transformer ce qui précède en un simple utilitaire de bibliothèque, comme je le ferais en Java, mais je pense qu'en Scala, ce n'est pas nécessaire).

254voto

Ben Lings Points 11395

Vous pouvez utiliser

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

mais attention, cela nécessite 2 traversées.

8 votes

Je préfère encore mes suggestions dans trac d'un Traversable[K].mapTo( K => V) y Traversable[V].mapBy( V => K) étaient meilleurs !

0 votes

Comme alternative, avec fermeture éclair : c map (_.getP) zip c toMap

7 votes

Soyez conscient qu'il s'agit d'une opération quadratique, mais il en va de même pour la plupart des autres variantes données ici. En regardant le code source de scala.collection.mutable.MapBuilder etc, il me semble que pour chaque tuple, une nouvelle map immuable est créée à laquelle le tuple est ajouté.

24voto

James Iry Points 14192

Vous pouvez construire une Map 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)

5 votes

Cela fait plus de deux semaines que je lis Scala et que je travaille sur des exemples et je n'ai jamais vu cette notation " : _ *" ! Merci beaucoup pour votre aide

0 votes

Pour mémoire, je me demande pourquoi nous avons besoin de préciser qu'il s'agit d'une séquence avec des _. . map convertit toujours une liste de tuple ici. Alors pourquoi le _ ? Je veux dire que cela fonctionne mais j'aimerais comprendre l'attribution des types ici.

1 votes

Cette méthode est-elle plus efficace que les autres ?

20voto

Daniel Spiewak Points 30706

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 des tuple (moins d'objets poubelle sont créés) :

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

0 votes

Je vais essayer ceci (je suis sûr que ça marche :-). Que se passe-t-il avec la fonction "(m,s)=>m(s) = s.length" ? J'ai vu l'exemple typique de foldLeft avec une somme et une fonction "_ + _" ; ceci est beaucoup plus déroutant ! La fonction semble supposer que j'ai déjà un tuple (m,s), ce que je ne comprends pas vraiment.

0 votes

Est c'est bien ça ? Selon la scaladoc de foldLeft : "foldLeft [B](z : B)(op : (B, A) => B) : B" B dans ce cas doit être une Map[String, Int], donc je ne comprends pas vraiment la fonction dans votre exemple du tout ! Elle devrait retourner une Map pour commencer, n'est-ce pas ?

0 votes

OK - donc j'ai ça ! "m(s) = s.length" (où m est une carte) renvoie une nouvelle carte avec le mapping "s -> s.length". Comment étais-je censé savoir cela ? Je ne le trouve nulle part dans les sections de programmation en scala sur les cartes !

9voto

Somatik Points 2440

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 permet d'éviter la création de la liste intermédiaire, plus d'infos ici : Scala 2.8 breakOut

1voto

missingfaktor Points 44003

Pour ce que ça vaut, en voici deux inutile des moyens 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))

0 votes

En outre, voici comment ces deux éléments se présenteraient en Haskell : Map.fromList $ map (bar &&& id) c , Map.fromList $ map (bar >>= (,)) c .

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X