Jaki jest najlepszy sposób na przekształcenie kolekcji w mapę według klucza?

165

Jeśli mam kolekcję ctypu Ti nie ma właściwość pna T(typu P, powiedzmy), co jest najlepszym sposobem, aby zrobić MAP-by-wydobycia kluczu ?

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

Jeden sposób jest następujący:

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

Ale teraz potrzebuję mutowalnej mapy. Czy jest lepszy sposób na zrobienie tego, aby było to w jednej linii, a ja otrzymałem niezmienną mapę? (Oczywiście mógłbym zamienić powyższe w proste narzędzie biblioteczne, tak jak zrobiłbym to w Javie, ale podejrzewam, że w Scali nie ma takiej potrzeby)

oxbow_lakes
źródło

Odpowiedzi:

232

Możesz użyć

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

ale pamiętaj, że wymaga to 2 przejść.

Ben Lings
źródło
8
Nadal wolę moje sugestie w trac a Traversable[K].mapTo( K => V)i Traversable[V].mapBy( V => K)były lepsze!
oxbow_lakes
7
Należy pamiętać, że jest to operacja kwadratowa, ale to samo dotyczy większości innych podanych tutaj wariantów. Patrząc na kod źródłowy scala.collection.mutable.MapBuilder itp., Wydaje mi się, że dla każdej krotki tworzona jest nowa niezmienna mapa, do której jest dodawana krotka.
jcsahnwaldt Przywróć Monikę
30
Na moim komputerze dla listy zawierającej 500 000 elementów ten kod Scali jest około 20 razy wolniejszy niż proste podejście Java (utwórz HashMap o odpowiednim rozmiarze, zapętlaj listę, wstaw elementy do mapy). Na 5000 elementów Scala jest około 8 razy wolniejsza. Podejście pętli napisane w Scali jest około 3 razy szybsze niż wariant toMap, ale nadal od 2 do 7 razy wolniejsze niż Java.
jcsahnwaldt Przywróć Monikę
8
Czy mógłbyś podać źródła testów społeczności SO? Dzięki.
user573215
8
Zastąpić cw c.iteratorcelu utworzenia powodują unikanie gromadzenia pośredniej.
ghik
21

Możesz skonstruować Mapę ze zmienną liczbą krotek. Więc użyj metody map na kolekcji, aby przekonwertować ją na kolekcję krotek, a następnie użyj sztuczki: _ *, aby przekonwertować wynik na zmienny argument.

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
źródło
5
Czytam o Scali przez ponad 2 tygodnie i pracuję nad przykładami i ani razu nie widziałem tego zapisu ": _ *"! Bardzo dziękuję za pomoc
oxbow_lakes
Tak dla porządku, zastanawiam się, dlaczego musimy sprecyzować, że jest to sekwencja z _ . map nadal konwertować zwraca listę krotek tutaj. Więc dlaczego _ ? To znaczy to działa, ale chciałbym zrozumieć przypisanie typu tutaj
MaatDeamon
1
Czy jest to bardziej wydajne niż inne metody?
Jus12
16

Oprócz rozwiązania @James Iry można to również osiągnąć za pomocą fałdy. Podejrzewam, że to rozwiązanie jest nieco szybsze niż metoda krotki (powstaje mniej śmieci):

val list = List("this", "maps", "string", "to", "length")
val map = list.foldLeft(Map[String, Int]()) { (m, s) => m(s) = s.length }
Daniel Śpiewak
źródło
Spróbuję tego (na pewno działa :-). Co się dzieje z funkcją "(m, s) => m (s) = s.length"? Widziałem typowy przykład foldLeft z sumą i funkcją „_ + _”; to jest o wiele bardziej zagmatwane! Funkcja wydaje się zakładać, że mam już krotkę (m, s), której tak naprawdę nie dostaję
oxbow_lakes
2
Stary, Scala była wtedy dziwna!
missingfaktor
8
@Daniel Próbuję twojego kodu, ale pojawia się następujący błąd: „aktualizacja wartości nie jest członkiem scala.collection.immutable.Map [String, Int]”. Proszę wyjaśnić swój kod, jak działa ten kod?
mr.boyfox,
1
nie wydaje się działać. dla mnie też „Aplikacja nie przyjmuje parametrów”
jayunit100
7
Niezmienna wersja: list.foldLeft(Map[String,Int]()) { (m,s) => m + (s -> s.length) }. Należy pamiętać, że jeśli chcesz używać przecinka zbudować krotki, trzeba dodatkową parę nawiasów: ((s, s.length)).
Kelvin
11

Można to zaimplementować niezmiennie i za pomocą pojedynczego przejścia, zwijając kolekcję w następujący sposób.

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

Rozwiązanie działa, ponieważ dodanie do niezmiennej mapy zwraca nową niezmienną mapę z dodatkowym wpisem, a ta wartość służy jako akumulator podczas operacji fold.

Kompromisem jest tutaj prostota kodu w porównaniu z jego wydajnością. Dlatego w przypadku dużych kolekcji to podejście może być bardziej odpowiednie niż użycie 2 implementacji przechodzenia, takich jak stosowanie mapi toMap.

RamV13
źródło
8

Inne rozwiązanie (może nie działać dla wszystkich typów)

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

pozwala to uniknąć tworzenia listy pośredników, więcej informacji tutaj: Scala 2.8 breakOut

Somatik
źródło
7

To, co próbujesz osiągnąć, jest nieco nieokreślone.
Co się stanie, jeśli co najmniej dwa elementy csą takie same p? Który element zostanie zmapowany do tego pna mapie?

Bardziej dokładnym sposobem spojrzenia na to jest przedstawienie mapy między pwszystkimi cprzedmiotami, które ją zawierają:

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

Można to łatwo osiągnąć dzięki groupBy :

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

Jeśli nadal potrzebujesz oryginalnej mapy, możesz na przykład zmapować pją na pierwszą t, która ją ma:

val m: Map[P, T] = c.groupBy(t => t.p) map { case (p, ts) =>  p -> ts.head }
Eyal Roth
źródło
1
Jednym z przydatnych ulepszeń jest użycie collectzamiast map. Np c.group(t => t.p) collect { case (Some(p), ts) => p -> ts.head }. : . W ten sposób możesz robić takie rzeczy, jak spłaszczanie map, gdy klucz jest Option [_].
healsjnr
@healsjnr Jasne, można to powiedzieć o każdej mapie. Nie jest to jednak główny problem.
Eyal Roth
1
Możesz użyć .mapValues(_.head)zamiast mapy.
lex82
2

Prawdopodobnie nie jest to najskuteczniejszy sposób przekształcenia listy w mapę, ale sprawia, że ​​kod wywołujący jest bardziej czytelny. Użyłem niejawnych konwersji, aby dodać metodę mapBy do listy:

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

Przykład kodu telefonicznego:

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

Należy zauważyć, że z powodu niejawnej konwersji kod wywołujący musi zaimportować implicitConversions scali.

Erez
źródło
2
c map (_.getP) zip c

Działa dobrze i jest bardzo intuicyjny

Jörg Bächtiger
źródło
8
Dodaj więcej szczegółów.
Syeda Zunaira
2
Przykro mi. Ale to JEST odpowiedź na pytanie „Scala najlepszy sposób na przekształcenie kolekcji w mapę według klucza?” jak Ben Lings.
Jörg Bächtiger
1
Ben nie podał żadnego wyjaśnienia?
shinzou
1
tworzy to dwie listy i łączy je w „mapę”, używając elementów cjako klucza (w pewnym sensie). Zwróć uwagę na „mapę”, ponieważ wynikowa kolekcja nie jest skalą, Mapale tworzy kolejną listę / iterowalną listę krotek ... ale efekt jest taki sam dla celów PO. Nie dyskontowałbym prostoty, ale nie jest tak wydajne jak foldLeftrozwiązanie, ani nie jest prawdziwa odpowiedź na pytanie „przekształcanie w zbiór w mapę według klucza”
Dexter Legaspi
2

A co powiesz na używanie zip i toMap?

myList.zip(myList.map(_.length)).toMap
AwesomeBobX64
źródło
1

Oto dwa bezsensowne sposoby na zrobienie tego:

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
źródło
Poza tym, fwiw, tak wyglądaliby ci dwaj w Haskell: Map.fromList $ map (bar &&& id) c, Map.fromList $ map (bar >>= (,)) c.
missingfaktor
-1

To działa dla mnie:

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

Mapa musi być zmienna, a mapa musi zostać zwrócona, ponieważ dodanie do mapy zmiennej nie zwraca mapy.

rustyfinger
źródło
1
W rzeczywistości można to zaimplementować niezmiennie w następujący sposób: val personsMap = persons.foldLeft(Map[Int, PersonDTO]()) { (m, p) => m + (p.id -> p) }Mapa może być niezmienna, jak pokazano powyżej, ponieważ dodanie do niezmiennej mapy zwraca nową niezmienną mapę z dodatkowym wpisem. Ta wartość służy jako akumulator podczas operacji składania.
RamV13
-2

użyj map () na kolekcji, a następnie toMap

val map = list.map(e => (e, e.length)).toMap
Krishna Kumar Chourasiya
źródło
3
Czym różni się to od odpowiedzi, która została przesłana i zaakceptowana 7 lat temu?
jwvh
-3

W przypadku konwersji z Json String (odczyt pliku json) do 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
źródło
To 11-letnie pytanie nie dotyczy JSON. Twoja odpowiedź jest nie na miejscu.
jwvh