wake-up-neo.com

Scala am besten eine Sammlung in eine Map-by-Key verwandeln?

Wenn ich eine Sammlung c vom Typ T habe und es eine Eigenschaft p für T gibt (z. B. vom Typ P), Was ist der beste Weg, um ein map-by-extracting-key zu machen?

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

Ein Weg ist der folgende:

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

Aber jetzt brauche ich eine wandelbare Karte. Gibt es eine bessere Möglichkeit, dies zu tun, so dass es in einer Zeile steht und ich eine nveränderliche Map erhalte? (Natürlich könnte ich das obige in ein einfaches Bibliotheksdienstprogramm verwandeln, wie ich es in Java tun würde, aber ich vermute, dass es in Scala keine Notwendigkeit gibt)

152
oxbow_lakes

Sie können verwenden

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

beachten Sie jedoch, dass dies zwei Durchgänge erfordert.

220
Ben Lings

Sie können eine Map mit einer variablen Anzahl von Tupeln erstellen. Verwenden Sie also die map-Methode für die Auflistung, um sie in eine Auflistung von Tupeln zu konvertieren, und verwenden Sie dann den Trick: _ *, um das Ergebnis in ein variables Argument zu konvertieren.

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)
19
James Iry

Zusätzlich zu der Lösung von @James Iry ist es auch möglich, dies mithilfe einer Falte zu erreichen. Ich vermute, dass diese Lösung etwas schneller ist als die Tuple-Methode (weniger Müllobjekte werden erstellt):

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

Dies kann unveränderlich und mit einer einzigen Überquerung implementiert werden, indem die Sammlung wie folgt durchgefaltet wird.

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

Die Lösung funktioniert, weil das Hinzufügen einer unveränderlichen Karte eine neue unveränderliche Karte mit dem zusätzlichen Eintrag zurückgibt und dieser Wert durch die Faltoperation als Akkumulator dient.

Der Nachteil hierbei ist die Einfachheit des Codes im Vergleich zu seiner Effizienz. Für große Sammlungen ist dieser Ansatz möglicherweise geeigneter als die Verwendung von zwei Durchquerungsimplementierungen, z. B. das Anwenden von map und toMap.

10
RamV13

Eine andere Lösung (funktioniert möglicherweise nicht bei allen Typen)

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

dies vermeidet die Erstellung der Zwischenliste, mehr Infos hier: Scala 2.8 breakOut

8
Somatik

Was Sie erreichen wollen, ist ein bisschen undefiniert.
Was passiert, wenn zwei oder mehr Elemente in c dasselbe p haben? Welches Element wird diesem p in der Karte zugeordnet?

Die genauere Sichtweise ergibt eine Karte zwischen p und allen c Elementen, die diese enthalten:

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

Dies könnte leicht mit groupBy erreicht werden:

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

Wenn Sie immer noch die ursprüngliche Zuordnung wünschen, können Sie beispielsweise p der ersten t zuordnen, die über diese verfügt:

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

Dies ist wahrscheinlich nicht die effizienteste Methode, um eine Liste in eine Karte umzuwandeln, macht den aufrufenden Code jedoch besser lesbar. Ich habe implizite Konvertierungen verwendet, um eine mapBy -Methode zu List hinzuzufügen:

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

Codebeispiel aufrufen:

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

Beachten Sie, dass der Aufrufercode aufgrund der impliziten Konvertierung die impliziten Konvertierungen von Scala importieren muss.

2
Erez
c map (_.getP) Zip c

Funktioniert gut und ist sehr intuitiv

2

Für das, was es wert ist, sind hier zwei sinnlose Möglichkeiten, es zu tun:

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
missingfaktor

Wie wäre es mit Zip und toMap?

myList.Zip(myList.map(_.length)).toMap
1
AwesomeBobX64