scalatutorial.de

Stufe 5Collections · Lektion 27 von 34

Map: Schlüssel und Werte

Aktualisiert am 24. September 20263 Min. LesezeitSchritt 3/4 dieser Stufe

Eine Map ordnet Schlüsseln Werte zu – Ländercodes zu Namen, Artikelnummern zu Preisen, Wörtern zu Häufigkeiten. In Scala ist sie unveränderlich, liefert fehlende Einträge als Option und lässt sich mit denselben Methoden auswerten wie Listen.

Maps erzeugen

Eine Map ordnet eindeutigen Schlüsseln Werte zu"de"Deutschland"fr"Frankreich"it"ItalienSchlüssel sind eindeutig,Werte dürfen sich wiederholen.m.get("es") // Nonem("de") // "Deutschland"
Jeder Schlüssel kommt genau einmal vor.
 Scala 3
val laender = Map(
  "de" -> "Deutschland",
  "fr" -> "Frankreich",
  "it" -> "Italien"
)

"de" -> "Deutschland" ist nur eine andere Schreibweise für das Tupel ("de", "Deutschland"). Eine Map ist also eine Sammlung von Paaren, bei der jeder Schlüssel nur einmal vorkommt. Kommt ein Schlüssel doppelt vor, gewinnt der letzte Eintrag.

Werte abfragen

 Scala 3
laender("de")                     // "Deutschland"
laender("es")                     // NoSuchElementException!
laender.get("es")                 // None
laender.get("fr")                 // Some("Frankreich")
laender.getOrElse("es", "unbekannt")   // "unbekannt"
laender.contains("it")            // true

Der direkte Zugriff mit Klammern wirft bei fehlendem Schlüssel eine Ausnahme. get liefert stattdessen eine Option und zwingt Sie, den Fall „nicht vorhanden“ zu bedenken. Für einen festen Ersatzwert gibt es withDefaultValue:

 Scala 3
val punkte = Map("Ada" -> 12).withDefaultValue(0)
punkte("Alan")   // 0

Einträge ändern

Maps sind unveränderlich; jede Änderung liefert eine neue Map:

 Scala 3
val mehr    = laender + ("es" -> "Spanien")
val anders  = laender.updated("it", "Italia")
val weniger = laender - "fr"            // oder laender.removed("fr")
val beide   = laender ++ Map("at" -> "Österreich", "ch" -> "Schweiz")

Für einen Wert, der aus dem bisherigen berechnet wird, eignet sich updatedWith:

 Scala 3
val lager = Map("Schraube" -> 120, "Mutter" -> 80)
lager.updatedWith("Schraube")(_.map(_ - 20))   // Schraube -> 100

Durchlaufen und umwandeln

 Scala 3
for (code, name) <- laender do println(s"$code: $name")

laender.keys                    // Iterable(de, fr, it)
laender.values                  // Iterable(Deutschland, Frankreich, Italien)
laender.map((k, v) => k.toUpperCase -> v.length)   // Map(DE -> 11, FR -> 10, IT -> 7)
laender.filter((_, v) => v.startsWith("F"))        // Map(fr -> Frankreich)
laender.view.mapValues(_.toUpperCase).toMap        // nur die Werte umwandeln

In Scala 3 dürfen Lambdas über Paare direkt zwei Parameter haben – (k, v) => … statt { case (k, v) => … }.

Maps aus Listen bauen

Oft entsteht eine Map aus einer Liste:

 Scala 3
case class Produkt(nummer: String, name: String, preis: BigDecimal)

val produkte = List(
  Produkt("A-1", "Tasse", 8),
  Produkt("A-2", "Teller", 12),
  Produkt("B-7", "Kanne", 29)
)

val nachNummer: Map[String, Produkt] = produkte.map(p => p.nummer -> p).toMap
nachNummer("B-7").name   // "Kanne"

val nachSerie = produkte.groupBy(_.nummer.take(1))
// Map(A -> List(Tasse, Teller), B -> List(Kanne))

val summeJeSerie = produkte.groupMapReduce(_.nummer.take(1))(_.preis)(_ + _)
// Map(A -> 20, B -> 29)

groupMapReduce gruppiert, wandelt um und fasst zusammen – in einem Schritt. Klassisches Beispiel ist das Zählen von Wörtern:

 Scala 3
val text = "die treppe hat stufen und die stufen haben kanten"
text.split(" ").toList.groupMapReduce(identity)(_ => 1)(_ + _)
// Map(die -> 2, stufen -> 2, treppe -> 1, …)

Reihenfolge

Wie beim Set ist die Reihenfolge einer normalen Map nicht festgelegt. Sortierte Schlüssel liefert SortedMap (intern ein TreeMap), die Einfügereihenfolge bewahrt ListMap bzw. VectorMap:

 Scala 3
import scala.collection.immutable.{SortedMap, VectorMap}

SortedMap("c" -> 3, "a" -> 1, "b" -> 2)   // TreeMap(a -> 1, b -> 2, c -> 3)
VectorMap("c" -> 3, "a" -> 1)             // Reihenfolge wie eingefügt

Veränderliche Maps

Für Zähler, Caches oder schrittweise aufgebaute Indizes gibt es mutable.Map:

 Scala 3
import scala.collection.mutable

val zaehler = mutable.Map.empty[String, Int].withDefaultValue(0)
for w <- List("a", "b", "a") do zaehler(w) += 1
zaehler   // HashMap(a -> 2, b -> 1)

val cache = mutable.Map.empty[Int, BigInt]
def teuer(n: Int): BigInt = cache.getOrElseUpdate(n, BigInt(2).pow(n))

getOrElseUpdate berechnet einen Wert nur beim ersten Mal und speichert ihn – eine einfache Form von Memoisierung.

Passt dazu