Il modo migliore di Scala di trasformare una collezione in una mappa per chiave?


165

Se ho una raccolta cdi tipi Te c'è una proprietà psu T(di tipo P, diciamo), qual è il modo migliore per fare una mappa per estrazione-chiave ?

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

Un modo è il seguente:

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

Ma ora ho bisogno di una mappa mutabile . C'è un modo migliore per farlo in modo che sia in 1 riga e finisca con una mappa immutabile ? (Ovviamente potrei trasformare quanto sopra in una semplice utility di libreria, come farei in Java, ma sospetto che a Scala non sia necessario)

Risposte:


232

Puoi usare

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

ma tieni presente che ciò richiede 2 traversate.


8
Preferisco ancora i miei suggerimenti in trac of a Traversable[K].mapTo( K => V)ed Traversable[V].mapBy( V => K)erano migliori!
oxbow_lakes,

7
Tieni presente che si tratta di un'operazione quadratica, ma lo stesso vale per la maggior parte delle altre varianti fornite qui. Osservando il codice sorgente di scala.collection.mutable.MapBuilder ecc., Mi sembra che per ogni tupla, venga creata una nuova mappa immutabile a cui viene aggiunta la tupla.
jcsahnwaldt Ripristina Monica

30
Sulla mia macchina per un elenco con 500.000 elementi, questo codice Scala è circa 20 volte più lento dell'approccio Java diretto (creare HashMap con dimensioni appropriate, loop over list, mettere elementi nella mappa). Per 5.000 elementi, Scala è circa 8 volte più lento. L'approccio loop scritto in Scala è circa 3 volte più veloce della variante toMap, ma comunque tra 2 e 7 volte più lento di Java.
jcsahnwaldt Ripristina Monica

8
Fornirebbe le fonti di prova alla comunità SO? Grazie.
user573215

8
Sostituire ccon c.iteratorper evitare la creazione di raccolte intermedie.
Ghik,

21

Puoi costruire una mappa con un numero variabile di tuple. Quindi usa il metodo map sulla raccolta per convertirlo in una raccolta di tuple e quindi usa il trucco: _ * per convertire il risultato in un argomento variabile.

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
Ho letto di Scala per> 2 settimane e ho lavorato su esempi e non una volta avevo visto questa notazione ": _ *"! Grazie mille per il tuo aiuto
oxbow_lakes

Solo per la cronaca, mi chiedo perché dobbiamo precisare che questa è una sequenza con _ . la mappa viene ancora convertita restituisce un elenco di tuple qui. Quindi perché il _ ? Voglio dire, funziona, ma vorrei capire la
descrizione

1
È più efficiente rispetto agli altri metodi?
Jus12

16

Oltre alla soluzione @James Iry, è anche possibile farlo usando una piega. Sospetto che questa soluzione sia leggermente più veloce del metodo tupla (vengono creati meno oggetti spazzatura):

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

Lo proverò (sono sicuro che funziona :-). Che cosa sta succedendo con la funzione "(m, s) => m (s) = s.length"? Ho visto il tipico esempio di foldLeft con una somma e una funzione "_ + _"; questo è molto più confuso! La funzione sembra presumere che io abbia già una tupla (m, s), che in realtà non capisco
oxbow_lakes

2
Amico, Scala era strano allora!
missingfaktor il

8
@Daniel Provo il tuo codice, ma compare il seguente errore: "L'aggiornamento del valore non è un membro di scala.collection.immutable.Map [String, Int]". Spiega il tuo codice come funziona questo codice?
mr.boyfox,

1
non sembra funzionare. nemmeno per me "L'applicazione non
accetta

7
Versione Immutabile: list.foldLeft(Map[String,Int]()) { (m,s) => m + (s -> s.length) }. Si noti che se si desidera utilizzare una virgola per costruire la tupla, è necessario un paio di parentesi: ((s, s.length)).
Kelvin,

11

Questo può essere implementato immutabilmente e con un unico attraversamento piegando la collezione come segue.

val map = c.foldLeft(Map[P, T]()) { (m, t) => m + (t.getP -> t) }

La soluzione funziona perché l'aggiunta a una mappa immutabile restituisce una nuova mappa immutabile con la voce aggiuntiva e questo valore funge da accumulatore durante l'operazione di piegatura.

Il compromesso qui è la semplicità del codice rispetto alla sua efficienza. Pertanto, per le raccolte di grandi dimensioni, questo approccio può essere più adatto rispetto all'utilizzo di 2 implementazioni trasversali come application mape toMap.


8

Un'altra soluzione (potrebbe non funzionare per tutti i tipi)

import scala.collection.breakOut
val m:Map[P, T] = c.map(t => (t.getP, t))(breakOut)

questo evita la creazione dell'elenco degli intermediari, maggiori informazioni qui: Scala 2.8 breakOut


7

Quello che stai cercando di ottenere è un po 'indefinito.
Cosa succede se due o più articoli in ccondividono lo stesso p? Quale elemento verrà mappato a quello pnella mappa?

Il modo più accurato di guardare a questo è produrre una mappa tra pe tutti gli celementi che la possiedono:

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

Questo potrebbe essere facilmente raggiunto con groupBy :

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

Se vuoi ancora la mappa originale, puoi, ad esempio, mappare palla prima tche l'ha:

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

1
Un utile trucco su questo è usare al collectposto di map. Ad esempio: c.group(t => t.p) collect { case (Some(p), ts) => p -> ts.head }. In questo modo è possibile eseguire operazioni come appiattire le mappe quando si digita un'opzione [_].
healsjnr,

@healsjnr Certo, questo si può dire per qualsiasi mappa. Non è il problema principale qui, però.
Eyal Roth,

1
È possibile utilizzare al .mapValues(_.head)posto della mappa.
lex82,

2

Questo probabilmente non è il modo più efficiente per trasformare un elenco in mappa, ma rende più leggibile il codice chiamante. Ho usato conversioni implicite per aggiungere un metodo mapBy all'elenco:

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
  }
}

Esempio di codice chiamante:

val list = List("A", "AA", "AAA")
list.mapBy(_.length)                  //Map(1 -> A, 2 -> AA, 3 -> AAA)

Si noti che a causa della conversione implicita, il codice chiamante deve importare le implicitConversions di Scala.


2
c map (_.getP) zip c

Funziona bene ed è molto intuitivo


8
Si prega di aggiungere maggiori dettagli.
Syeda Zunaira,

2
Mi dispiace. Ma questa è una risposta alla domanda "Il modo migliore di Scala di trasformare una collezione in una mappa per chiave?" come Ben Lings.
Jörg Bächtiger,

1
E Ben non ha fornito alcuna spiegazione?
shinzou,

1
questo crea due elenchi e li combina in una "mappa" usando gli elementi ccome chiave (sorta di). Nota "mappa" perché la raccolta risultante non è una scala Mapma crea un altro elenco / iterabile di tuple ... ma l'effetto è lo stesso per lo scopo del PO non scarterei la semplicità ma non è efficiente come la foldLeftsoluzione, né è la vera risposta alla domanda "convertirsi in una raccolta in una mappa per chiave"
Dexter Legaspi,

2

Che ne dici di usare zip e toMap?

myList.zip(myList.map(_.length)).toMap

1

Per quello che vale, ecco due modi inutili per farlo:

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))

Inoltre, così è come apparirebbero quei due in Haskell: Map.fromList $ map (bar &&& id) c, Map.fromList $ map (bar >>= (,)) c.
missingfaktor il

-1

Questo funziona per me:

val personsMap = persons.foldLeft(scala.collection.mutable.Map[Int, PersonDTO]()) {
    (m, p) => m(p.id) = p; m
}

La mappa deve essere modificabile e la mappa deve essere restituita poiché l'aggiunta a una mappa mutabile non restituisce una mappa.


1
In realtà, può essere implementato immutabilmente come segue: val personsMap = persons.foldLeft(Map[Int, PersonDTO]()) { (m, p) => m + (p.id -> p) }La mappa può essere immutabile, come evidenziato sopra, perché l'aggiunta a una mappa immutabile restituisce una nuova mappa immutabile con la voce aggiuntiva. Questo valore funge da accumulatore durante l'operazione di piegatura.
RamV13,

-2

usa map () sulla collezione seguito da toMap

val map = list.map(e => (e, e.length)).toMap

3
In che modo differisce dalla risposta che è stata presentata e accettata 7 anni fa?
jwvh,

-4

Se si converte da Json String (leggendo un file json) in 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)

Questa domanda di 11 anni non fa nulla su JSON. La tua risposta è fuori posto.
jwvh
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.