2009-03-23 17 views
121

Jeśli mam kolekcję c typu T i nie jest własnością p na T (typu P, powiedzmy), co jest najlepszym sposobem, aby zrobić mapa po ekstrakcji-key?Scala najlepszy sposób przekształcania kolekcji w mapę po klawiszu?

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

Jednym ze sposobów jest następująca:

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

Ale teraz muszę zmienny mapę. Czy jest lepszy sposób na zrobienie tego, aby był w jednej linii, a kończy na niezmiennej niezmiennej Map? (Oczywiście mogłem zamienić powyższe w proste narzędzie biblioteki, tak jak w Javie, ale podejrzewam, że w Scali nie ma potrzeby)

Odpowiedz

183

Można użyć

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

ale należy pamiętać, że to wymaga przechodzenia między 2.

+8

Nadal wolę moje sugestie w trendzie 'Traversable [K] .mapTo (K => V)' i 'Traversalable [V] .mapBy (V => K)' były lepsze! –

+0

Jako alternatywa, zip: 'c map (_.getP) zip c toMap' – onof

+6

Należy pamiętać, że jest to operacja kwadratowa, ale to samo dotyczy większości innych wariantów tutaj podanych. 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 dodawana jest krotka. –

14

Można zbudować mapę ze zmienną liczbą krotek. Tak więc użyj metody map w kolekcji, aby przekształcić ją w kolekcję krotek, a następnie użyj: _ * trick, 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) 
+5

Czytałem o Scala przez> 2 tygodnie i działa poprzez przykłady i nie raz nie widziałem tego ": _ *" notację! Bardzo dziękuję za pomoc –

+0

Właśnie na ten rekord, zastanawiam się, dlaczego musimy sprecyzować, że jest to sekwencja z _ *. mapa nadal konwertuje zwróć listę krotki tutaj. Dlaczego więc _ *? Mam na myśli, że to działa, ale chciałbym zrozumieć opis typu tutaj – MaatDeamon

+0

Czy jest to bardziej wydajne niż inne metody? – Jus12

10

Oprócz rozwiązania @James Iry, można to również zrobić za pomocą zakładki. Podejrzewam, że to rozwiązanie jest nieco szybciej niż metoda krotki (mniej obiekty są tworzone na śmieci):

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

Spróbuję tego (jestem pewien, że to 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 mylące! Funkcja wydaje się zakładać, że mam już krotkę (m, s), której tak naprawdę nie otrzymuję –

+0

* Czy * to prawda? Zgodnie ze skaladoc foldLeft: \t "foldLeft [B] (z: B) (op: (B, A) => B): B" B w tym przypadku musi być mapą [String, Int], więc Naprawdę nie rozumiem tej funkcji w twoim przykładzie! Powinien zwrócić mapę na początek, czyż nie? –

+0

OK - więc mam to! "m (s) = s.length" (gdzie m jest mapą) zwraca nową mapę z mapowaniem "s -> s.length". Skąd miałem to wiedzieć? Nie mogę go znaleźć nigdzie w programowaniu w sekcjach scala na mapach! –

1

Na co warto, tu są dwa bezsensowne sposoby robi:

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

Także, fwiw, to jest jak ci dwaj będą wyglądać w Haskell: 'Map.fromList $ map (bar && & id) c',' Map.fromList $ map (bar >> = (,)) c'. – missingfaktor

6

Innym rozwiązaniem (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) 

to unika się tworzenia lista pośredników, więcej informacji tutaj: Scala 2.8 breakOut

1

To chyba nie jest najlepszy Np sposób, aby zmienić listę na mapę, ale sprawia, że ​​kod wywołujący jest bardziej czytelny. Kiedyś niejawne konwersje dodać mapBy metody 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 
    } 
} 

Wywołanie przykład kodu:

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

Należy pamiętać, że ze względu na niejawna konwersja, kod rozmówca musi importować implicitConversions Scala.

2
c map (_.getP) zip c 

działa dobrze i jest bardzo Intuitiv

+6

Proszę dodać więcej szczegółów –

+2

Przepraszam Ale to jest odpowiedź na pytanie "Scala najlepszy sposób przekształcenia kolekcji w mapę za kluczem?", Jak Ben Lings: –

+1

A Ben nie przedstawił żadnych wyjaśnień? – shinzou

-1

Działa to dla mnie:

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

Mapa ma być zmienny i mapa musi być powrót ponieważ dodanie do zmienny mapa nie zwróć mapę.

+1

W rzeczywistości może być zaimplementowana 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 widać powyżej, ponieważ dodanie do niezmiennej mapy zwraca nową niezmienną Mapa z dodatkowym wpisem. Ta wartość służy jako akumulator przez operację składania. – RamV13

4

To, co próbujesz osiągnąć, jest nieco nieokreślone.
Co się stanie, jeśli dwie lub więcej pozycji w c ma tę samą p? Który element zostanie przyporządkowany do tego p na mapie?

Im bardziej dokładny sposób patrzenia na ten przynosi mapę pomiędzy p i wszystkie c elementy, które go:

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

ten można łatwo osiągnąć groupBy:

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

Jeśli nadal chcesz oryginalnej mapy, możesz na przykład mapować p na pierwszą t, która ma:

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

Jedną z przydatnych poprawek jest użycie '' 'collect''' zamiast' '' map'''. Np .: '' 'c.group (t => t.p) collect {case (Some (p), ts) => p -> ts.head}' ''. W ten sposób możesz np. Spłaszczyć mapy, gdy klucz jest opcją [_]. – healsjnr

+0

@healsjnr Oczywiście, można to powiedzieć na każdej mapie. Nie jest to jednak sedno sprawy. –

5

Można to zaimplementować niezmiennie i jednym ruchem, składają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 mapą zwraca nowy niezmienny mapę z dodatkowym wejściem i wartość ta służy jako akumulator przez krotnym operacji.

Kompromisem tutaj jest prostota kodu, a jego efektywność. Tak więc w przypadku dużych kolekcji podejście to może być bardziej odpowiednie niż stosowanie dwóch implementacji, takich jak stosowanie map i toMap.

-1

wykorzystanie map() w kolekcji, a następnie z toMap

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

Czym różni się to od odpowiedzi, która została przesłana i zaakceptowana 7 lat temu? – jwvh