¿Es la mejor forma de convertir una colección en un mapa por clave?

165

Si tengo una colección cde tipos Ty hay una propiedad pen T(de tipo P, por ejemplo), ¿cuál es la mejor manera de hacer una clave de mapa por extracción ?

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

Una forma es la siguiente:

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

Pero ahora necesito un mapa mutable . ¿Hay una mejor manera de hacer esto para que esté en 1 línea y termine con un Mapa inmutable ? (Obviamente, podría convertir lo anterior en una simple utilidad de biblioteca, como lo haría en Java, pero sospecho que en Scala no hay necesidad)

oxbow_lakes
fuente

Respuestas:

232

Puedes usar

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

pero tenga en cuenta que esto necesita 2 recorridos.

Ben Lings
fuente
8
¡Todavía prefiero mis sugerencias en trac de a Traversable[K].mapTo( K => V)y Traversable[V].mapBy( V => K)fueron mejores!
oxbow_lakes
77
Tenga en cuenta que esta es una operación cuadrática, pero lo mismo ocurre con la mayoría de las otras variantes dadas aquí. Al observar el código fuente de scala.collection.mutable.MapBuilder, etc., me parece que para cada tupla, se crea un nuevo mapa inmutable al que se agrega la tupla.
jcsahnwaldt Restablece a Monica
30
En mi máquina para una lista con 500,000 elementos, este código Scala es aproximadamente 20 veces más lento que el enfoque directo de Java (crear HashMap con el tamaño apropiado, recorrer la lista, colocar elementos en el mapa). Para 5.000 elementos, Scala es aproximadamente 8 veces más lento. El enfoque de bucle escrito en Scala es aproximadamente 3 veces más rápido que la variante toMap, pero sigue siendo entre 2 y 7 veces más lento que Java.
jcsahnwaldt Restablece a Monica el
8
¿Podría proporcionar las fuentes de prueba a la comunidad SO? Gracias.
user573215
8
Reemplace ccon c.iteratorpara evitar la creación de una colección intermedia.
ghik
21

Puede construir un mapa con un número variable de tuplas. Por lo tanto, use el método de mapa en la colección para convertirlo en una colección de tuplas y luego use el truco: _ * para convertir el resultado en un argumento 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
fuente
55
¡He estado leyendo sobre Scala durante> 2 semanas y trabajando en ejemplos y ni una vez había visto esta notación ": _ *"! Muchas gracias por su ayuda
oxbow_lakes
Solo para que conste, me pregunto por qué necesitamos precisar que esta es una secuencia con _ . mapa todavía convertir devolver una lista de tuplas aquí. Entonces, ¿por qué el _ ? Quiero decir que funciona, pero me gustaría entender la adscripción de tipo aquí
MaatDeamon
1
¿Es esto más eficiente que los otros métodos?
Jus12
16

Además de la solución de @James Iry, también es posible lograr esto usando un pliegue. Sospecho que esta solución es un poco más rápida que el método de tupla (se crean menos objetos basura):

val list = List("this", "maps", "string", "to", "length")
val map = list.foldLeft(Map[String, Int]()) { (m, s) => m(s) = s.length }
Daniel Spiewak
fuente
Probaré esto (estoy seguro de que funciona :-). ¿Qué está pasando con la función "(m, s) => m (s) = s.length"? He visto el típico ejemplo de foldLeft con una suma y una función "_ + _"; ¡Esto es mucho más confuso! La función parece suponer que ya tengo una tupla (m, s), lo que realmente no obtengo
Oxbow_lakes
2
Hombre, Scala era raro en ese entonces!
missingfaktor
8
@Daniel Intento su código, pero aparece el siguiente error: "la actualización del valor no es miembro de scala.collection.immutable.Map [String, Int]". Por favor, explique su código cómo funciona este código?
mr.boyfox
1
no parece funcionar. para mí "La aplicación no toma parámetros"
jayunit100
77
Versión inmutable: list.foldLeft(Map[String,Int]()) { (m,s) => m + (s -> s.length) }. Tenga en cuenta que si quieres usar una coma para construir la tupla, necesita un par adicional de paréntesis: ((s, s.length)).
Kelvin
11

Esto se puede implementar de manera inmutable y con un solo recorrido al plegar la colección de la siguiente manera.

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

La solución funciona porque agregar a un Mapa inmutable devuelve un nuevo Mapa inmutable con la entrada adicional y este valor sirve como acumulador a través de la operación de plegado.

La desventaja aquí es la simplicidad del código versus su eficiencia. Por lo tanto, para colecciones grandes, este enfoque puede ser más adecuado que usar 2 implementaciones transversales como la aplicación mapy toMap.

RamV13
fuente
8

Otra solución (puede que no funcione para todos los tipos)

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

esto evita la creación de la lista de intermediarios, más información aquí: Scala 2.8 breakOut

Somatik
fuente
7

Lo que estás tratando de lograr es un poco indefinido.
¿Qué pasa si dos o más elementos ccomparten lo mismo p? ¿Qué elemento se asignará a eso pen el mapa?

La forma más precisa de ver esto es generar un mapa entre py todos los celementos que lo tienen:

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

Esto podría lograrse fácilmente con groupBy :

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

Si aún desea el mapa original, puede, por ejemplo, asignar pal primero tque lo tiene:

val m: Map[P, T] = c.groupBy(t => t.p) map { case (p, ts) =>  p -> ts.head }
Eyal Roth
fuente
1
Una modificación práctica sobre esto es usar en collectlugar de map. Por ejemplo: c.group(t => t.p) collect { case (Some(p), ts) => p -> ts.head }. De esta forma, puede hacer cosas como aplanar mapas cuando la tecla es una Opción [_].
healsjnr
@healsjnr Claro, esto podría decirse de cualquier mapa. Sin embargo, no es el tema central aquí.
Eyal Roth
1
Podrías usar en .mapValues(_.head)lugar del mapa.
lex82
2

Probablemente esta no sea la forma más eficiente de convertir una lista en un mapa, pero hace que el código de llamada sea más legible. Usé conversiones implícitas para agregar un método mapBy a 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
  }
}

Ejemplo de código de llamada:

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

Tenga en cuenta que debido a la conversión implícita, el código de la persona que llama necesita importar las conversiones implícitas de scala.

Erez
fuente
2
c map (_.getP) zip c

Funciona bien y es muy intuitivo.

Jörg Bächtiger
fuente
8
Por favor agregue más detalles.
Syeda Zunaira
2
Lo siento. Pero, esta es una respuesta a la pregunta "¿La mejor manera de Scala de convertir una colección en un mapa por clave?" como es Ben Lings.
Jörg Bächtiger
1
¿Y Ben no dio ninguna explicación?
Shinzou
1
esto crea dos listas y se combinan en un "mapa" usando los elementos ccomo clave (más o menos). Tenga en cuenta "map" porque la colección resultante no es una escala Mapsino que crea otra lista / iterable de tuplas ... pero el efecto es el mismo para el propósito del OP, no descartaría la simplicidad pero no es tan eficiente como la foldLeftsolución, ni es la verdadera respuesta a la pregunta "convertir en una colección en un mapa por clave"
Dexter Legaspi
2

¿Qué tal usar zip y toMap?

myList.zip(myList.map(_.length)).toMap
AwesomeBobX64
fuente
1

Por lo que vale, aquí hay dos formas inútiles de hacerlo:

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))
missingfaktor
fuente
Además, así es como se verían esos dos en Haskell: Map.fromList $ map (bar &&& id) c, Map.fromList $ map (bar >>= (,)) c.
missingfaktor
-1

Esto funciona para mi:

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

El mapa debe ser mutable y el mapa debe ser devuelto ya que agregarlo a un mapa mutable no devuelve un mapa.

rustyfinger
fuente
1
En realidad, se puede implementar de manera inmutable de la siguiente manera: val personsMap = persons.foldLeft(Map[Int, PersonDTO]()) { (m, p) => m + (p.id -> p) }el mapa puede ser inmutable, como se evidencia anteriormente, porque al agregarlo a un mapa inmutable, se devuelve un nuevo mapa inmutable con la entrada adicional. Este valor sirve como acumulador a través de la operación de plegado.
RamV13
-2

use map () en la colección seguido de toMap

val map = list.map(e => (e, e.length)).toMap
Krishna Kumar Chourasiya
fuente
3
¿En qué se diferencia esto de la respuesta que se envió y aceptó hace 7 años?
jwvh
-4

Si se convierte de Json String (leyendo un archivo json) a 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
fuente
Esta pregunta de 11 años no hace nada sobre JSON. Tu respuesta está fuera de lugar.
jwvh