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)

131
demandé sur Eugene Yokota 2009-03-23 21:26:59

11 réponses

Vous pouvez utiliser

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

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

194
répondu Ben Lings 2016-10-20 10:47:52

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)
16
répondu James Iry 2009-03-23 21:11:37

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 }
14
répondu Daniel Spiewak 2009-03-24 18:39:42

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.

9
répondu RamV13 2016-12-13 17:49:15

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

6
répondu Somatik 2017-05-23 11:47:04

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 }
5
répondu Eyal Roth 2015-12-03 16:37:54
c map (_.getP) zip c

Fonctionne bien et est très intuitiv

2
répondu Jörg Bächtiger 2014-12-04 10:37:56

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))
1
répondu missingfaktor 2012-02-04 10:07:23

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.

1
répondu Erez 2014-07-27 06:22:49

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.

-1
répondu rustyfinger 2015-11-30 21:14:56

Utilisez map () sur la collection suivie de toMap

val map = list.map(e => (e, e.length)).toMap
-3
répondu Krishna Kumar Chourasiya 2017-12-24 11:40:05