Scala najlepszym sposobem na przekształcenie kolekcji w Mapę po kluczu?

Jeśli mam zbiór c typu T i istnieje właściwość p na T (typu P, powiedzmy), jaki jest najlepszy sposób na zrobienie map-by-extracting-key ?

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, aby zrobić to tak, że jest w 1 linii i kończy się niezmienna Mapa? (Oczywiście mógłbym zamienić powyższe w proste narzędzie biblioteczne, tak jak w Javie, ale podejrzewam, że w Scali nie ma potrzeby)

Author: Eugene Yokota, 2009-03-23

11 answers

Możesz użyć

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

Ale należy pamiętać, że to wymaga 2 trawersy.

 194
Author: Ben Lings,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-10-20 10:47:52

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

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)
 16
Author: James Iry,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-03-23 21:11:37

Oprócz rozwiązania @ James Iry, możliwe jest również osiągnięcie tego za pomocą fałdy. Podejrzewam, że to rozwiązanie jest nieco szybsze niż metoda tuple (powstaje mniej obiektów śmieci):

val list = List("this", "maps", "string", "to", "length")
val map = list.foldLeft(Map[String, Int]()) { (m, s) => m(s) = s.length }
 14
Author: Daniel Spiewak,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-03-24 18:39:42

Można to zaimplementować niezmiennie i za pomocą pojedynczego przejścia przez zbiór 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 i ta wartość służy jako akumulator w operacji składania.

Kompromis polega tutaj na prostocie kodu a jego skuteczności. Tak więc w przypadku dużych zbiorów podejście to może być bardziej odpowiednie niż zastosowanie 2 implementacji takich jako zastosowanie map i toMap.

 9
Author: RamV13,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-12-13 17:49:15

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

 6
Author: Somatik,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:47:04

To, co próbujesz osiągnąć, jest trochę nieokreślone.
Co jeśli dwa lub więcej elementów c mają ten sam p? Który element zostanie zmapowany do p na mapie?

Dokładniejszym sposobem patrzenia na to jest uzyskanie mapy pomiędzy p A wszystkimi c przedmiotami, które ją posiadają:

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 chcesz oryginalną mapę, możesz na przykład mapować p do pierwszej t, która ma it:

val m: Map[P, T] = c.groupBy(t => t.p) map { case (p, ts) =>  p -> ts.head }
 5
Author: Eyal Roth,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-03 16:37:54
c map (_.getP) zip c

Działa dobrze i jest bardzo intuicyjny

 2
Author: Jörg Bächtiger,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-12-04 10:37:56

Jeśli to coś warte, 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))
 1
Author: missingfaktor,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-02-04 10:07:23

To prawdopodobnie nie jest najskuteczniejszy sposób na przekształcenie 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 wywołującego:

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

Zauważ, że ze względu na niejawną konwersję, kod wywołujący musi zaimportować niejawne konwersje Scali.

 1
Author: Erez,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-07-27 06:22:49

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 i mapa musi być zwrócona, ponieważ dodanie do mapy zmiennej nie zwraca mapy.

 -1
Author: rustyfinger,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-11-30 21:14:56

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

val map = list.map(e => (e, e.length)).toMap
 -3
Author: Krishna Kumar Chourasiya,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-12-24 11:40:05