web-dev-qa-db-fra.com

Scala meilleure façon de transformer une collection en 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 map-by-extracting-key?

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 modifiable. Y a-t-il une meilleure façon de le faire pour que ce soit en 1 ligne et que je me retrouve avec une carte immuable? (Évidemment, je pourrais transformer ce qui précède en un utilitaire de bibliothèque simple, comme je le ferais en Java, mais je soupçonne que dans Scala, il n’est pas nécessaire)

152
oxbow_lakes

Vous pouvez utiliser

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

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

220
Ben Lings

Vous pouvez construire une carte avec un nombre variable de n-uplets. Utilisez donc la méthode map de la collection pour la convertir en une collection de n-uplets, 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)
19
James Iry

En plus de la solution de @James Iry, il est également possible de réaliser 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 }
15
Daniel Spiewak

Cela 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 lors de 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 l’utilisation de 2 implémentations de parcours telles que l’application map et toMap.

10
RamV13

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

8
Somatik

Ce que vous essayez de réaliser est un peu indéfini.
Que faire si deux ou plusieurs éléments de c partagent le même p? Quel élément sera associé à ce p sur la carte?

La façon la plus précise de voir cela consiste à créer une carte entre p et tous les éléments c qui en sont dotés:

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

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

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

Si vous souhaitez toujours la carte d'origine, vous pouvez, par exemple, mapper p vers le premier t qui l'a:

val m: Map[P, T] = c.groupBy(t => t.p) map { case (p, ts) =>  p -> ts.head }
6
Eyal Roth

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 ajouter une méthode mapBy à List:

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 de l'appelant doit importer les conversions implicites de scala.

2
Erez
c map (_.getP) Zip c

Fonctionne bien et est très intuitif

2
Jörg Bächtiger

Pour ce que cela vaut, voici deux façons 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))
1
missingfaktor

Comment utiliser Zip et toMap?

myList.Zip(myList.map(_.length)).toMap
1
AwesomeBobX64