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

165

Si j'ai une collection cde type Tet qu'il y a une propriété psur T(de type P, par exemple), quelle est la meilleure façon de faire une carte par clé d'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 que ce soit sur 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, ce n'est pas nécessaire)

oxbow_lakes
la source

Réponses:

232

Vous pouvez utiliser

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

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

Ben Lings
la source
8
Je préfère quand même mes suggestions en trac d'un Traversable[K].mapTo( K => V)et Traversable[V].mapBy( V => K)c'était mieux!
oxbow_lakes
7
Sachez 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 carte immuable est créée à laquelle le tuple est ajouté.
jcsahnwaldt Réintègre Monica
30
Sur ma machine pour une liste de 500000 éléments, ce code Scala est environ 20 fois plus lent que l'approche Java simple (créer HashMap avec la taille appropriée, boucler la liste, mettre des éléments dans la carte). Pour 5 000 éléments, Scala est environ 8 fois plus lent. L'approche en boucle écrite en Scala est environ 3 fois plus rapide que la variante toMap, mais toujours entre 2 et 7 fois plus lente que Java.
jcsahnwaldt Réintègre Monica
8
Pourriez-vous s'il vous plaît fournir les sources de test à la communauté SO? THX.
user573215
8
Remplacez cpar c.iteratorpour éviter la création d'une collection intermédiaire.
ghik
21

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 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)
James Iry
la source
5
Je lis sur Scala depuis plus de 2 semaines et je travaille sur des exemples et pas une seule fois je n'avais vu cette notation ": _ *"! Merci beaucoup pour votre aide
oxbow_lakes
Pour mémoire, je me demande pourquoi nous devons préciser qu'il s'agit d'une séquence avec _ . map convertit toujours retourne une liste de tuple ici. Alors pourquoi le _ ? Je veux dire que cela fonctionne mais je voudrais comprendre l'attribution de type ici
MaatDeamon
1
Est-ce plus efficace que les autres méthodes?
Jus12
16

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 }
Daniel Spiewak
la source
Je vais essayer ceci (je suis sûr que cela fonctionne :-). 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 "_ + _"; c'est beaucoup plus déroutant! La fonction semble supposer que j'ai déjà un tuple (m, s), que je ne comprends pas vraiment
oxbow_lakes
2
Mec, Scala était bizarre à l'époque!
missingfaktor
8
@Daniel J'essaye votre code, mais apparaît l'erreur suivante: "value update n'est pas membre de scala.collection.immutable.Map [String, Int]". Veuillez expliquer votre code comment travailler ce code?
mr.boyfox
1
ne semble pas fonctionner.pour moi non plus "L'application ne prend pas de paramètres"
jayunit100
7
Version Immuable: list.foldLeft(Map[String,Int]()) { (m,s) => m + (s -> s.length) }. Notez que si vous souhaitez utiliser des virgules pour construire le tuple, vous avez besoin d' une paire de parenthèses: ((s, s.length)).
Kelvin
11

Cela peut être implémenté de manière immuable et avec un seul parcours en repliant 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 retourne une nouvelle carte immuable avec l'entrée supplémentaire et cette valeur sert d'accumulateur via l'opération de repli.

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 traversée telles que l'application mapet toMap.

RamV13
la source
8

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

Somatik
la source
7

Ce que vous essayez d'atteindre est un peu indéfini.
Que faire si deux ou plusieurs éléments cpartagent le même p? Quel élément sera mappé à celui pde la carte?

La façon la plus précise de voir cela est de produire une carte entre pet tous les céléments qui la contiennent:

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

Cela pourrait ê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 psur la première tqui la possède:

val m: Map[P, T] = c.groupBy(t => t.p) map { case (p, ts) =>  p -> ts.head }
Eyal Roth
la source
1
Un ajustement pratique à ce sujet est d'utiliser à la collectplace de map. Par exemple: c.group(t => t.p) collect { case (Some(p), ts) => p -> ts.head }. De cette façon, vous pouvez faire des choses comme aplatir les cartes lorsque vous tapez une option [_].
healsjnr
@healsjnr Bien sûr, cela pourrait être dit pour n'importe quelle carte. Ce n'est cependant pas le problème central ici.
Eyal Roth
1
Vous pouvez utiliser à la .mapValues(_.head)place de la carte.
lex82
2

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 implicitConversions de scala.

Erez
la source
2
c map (_.getP) zip c

Fonctionne bien et est très intuitif

Jörg Bächtiger
la source
8
Veuillez ajouter plus de détails.
Syeda Zunaira
2
Je suis désolé. Mais, ceci EST une réponse à la question "La meilleure façon Scala de transformer une collection en une carte par clé?" comme Ben Lings.
Jörg Bächtiger
1
Et Ben n'a fourni aucune explication?
shinzou
1
cela crée deux listes et les combine en une "carte" en utilisant les éléments ccomme clé (en quelque sorte). Notez "map" car la collection résultante n'est pas une scala Mapmais crée une autre liste / itérable de tuples ... mais l'effet est le même pour le but de l'OP, je n'écarterais pas la simplicité mais ce n'est pas aussi efficace que la foldLeftsolution, ni la vraie réponse à la question "convertir en une collection en une carte par clé"
Dexter Legaspi
2

Que diriez-vous d'utiliser zip et toMap?

myList.zip(myList.map(_.length)).toMap
AwesomeBobX64
la source
1

Pour ce que ça 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))
manquant
la source
Aussi, fwiw, voici à quoi ces deux ressembleraient dans Haskell: Map.fromList $ map (bar &&& id) c, Map.fromList $ map (bar >>= (,)) c.
missingfaktor
-1

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.

rouillé
la source
1
En fait, il peut être implémenté immuablement comme suit: val personsMap = persons.foldLeft(Map[Int, PersonDTO]()) { (m, p) => m + (p.id -> p) }La carte peut être immuable, comme indiqué ci-dessus, car l'ajout à une carte immuable renvoie une nouvelle carte immuable avec l'entrée supplémentaire. Cette valeur sert d'accumulateur pendant l'opération de pliage.
RamV13
-2

utiliser map () sur la collection suivi de toMap

val map = list.map(e => (e, e.length)).toMap
Krishna Kumar Chourasiya
la source
3
En quoi est-ce différent de la réponse qui a été soumise et acceptée il y a 7 ans?
jwvh
-4

En cas de conversion de Json String (lecture d'un fichier json) en scala Map

import spray.json._
import DefaultJsonProtocol._

val jsonStr = Source.fromFile(jsonFilePath).mkString
val jsonDoc=jsonStr.parseJson
val map_doc=jsonDoc.convertTo[Map[String, JsValue]]

// Get a Map key value
val key_value=map_doc.get("key").get.convertTo[String]

// If nested json, re-map it.
val key_map=map_doc.get("nested_key").get.convertTo[Map[String, JsValue]]
println("Nested Value " + key_map.get("key").get)
Ajit Surendran
la source
Cette question vieille de 11 ans ne pose rien sur JSON. Votre réponse est déplacée.
jwvh