Stufe 5Collections · Lektion 25 von 34
List und ihre Funktionen
List ist die bekannteste Collection in Scala: unveränderlich, einfach verkettet und mit Dutzenden Methoden ausgestattet, die Schleifen fast überflüssig machen. Wer List beherrscht, kann die meisten anderen Collections sofort mitbenutzen.
Listen erzeugen
val zahlen = List(4, 8, 15, 16, 23, 42)
val leer = List.empty[String] // oder Nil
val bereich = (1 to 5).toList // List(1, 2, 3, 4, 5)
val gleich = List.fill(3)("x") // List(x, x, x)
val quadrate = List.tabulate(5)(i => i * i) // List(0, 1, 4, 9, 16)Alle Elemente einer Liste haben denselben Typ. Mischen Sie Typen, wählt der Compiler einen gemeinsamen Obertyp – aus List(1, "a") wird eine Liste von Matchable bzw. Any. Das ist fast nie gewollt.
Aufbau: Kopf und Rest
Eine Liste ist entweder leer (Nil) oder besteht aus einem ersten Element (head) und dem Rest (tail), der wieder eine Liste ist. Der Operator :: (gesprochen „cons“) baut eine neue Liste, indem er ein Element vorne anhängt:
val xs = 1 :: 2 :: 3 :: Nil // List(1, 2, 3)
val ys = 0 :: xs // List(0, 1, 2, 3) – xs bleibt unverändert
xs.head // 1
xs.tail // List(2, 3)Weil ys sich die Zellen von xs teilt, kostet das Voranstellen nur eine neue Zelle – unabhängig von der Länge. Das ist möglich, weil Listen unveränderlich sind: Niemand kann xs nachträglich ändern.
Mit demselben Operator werden Listen im Pattern Matching zerlegt: case kopf :: rest => ….
Die wichtigsten Methoden
val preise = List(12.5, 3.0, 49.9, 7.25)
preise.map(_ * 1.19) // alle umrechnen
preise.filter(_ > 10) // List(12.5, 49.9)
preise.filterNot(_ > 10) // List(3.0, 7.25)
preise.sum // 72.65
preise.max // 49.9
preise.sorted // aufsteigend
preise.sortBy(-_) // absteigend
preise.exists(_ > 40) // true
preise.forall(_ > 1) // true
preise.count(_ < 10) // 2
preise.find(_ > 20) // Some(49.9)
preise.take(2) // List(12.5, 3.0)
preise.drop(2) // List(49.9, 7.25)
preise.partition(_ > 10) // (List(12.5, 49.9), List(3.0, 7.25))
preise.distinct // ohne Dubletten
preise.reverseKeine dieser Methoden verändert preise – jede liefert eine neue Liste oder einen neuen Wert.
Zusammenfassen: fold und reduce
val woerter = List("Stufe", "für", "Stufe")
woerter.foldLeft(0)((summe, w) => summe + w.length) // 13
woerter.reduce(_ + " " + _) // "Stufe für Stufe"
woerter.mkString(" ") // dasselbe, lesbarerfoldLeft beginnt mit einem Startwert und arbeitet die Liste von links ab. reduce nimmt das erste Element als Startwert und wirft bei einer leeren Liste eine Ausnahme – reduceOption ist die sichere Variante.
Gruppieren und verschachteln
case class Artikel(name: String, kategorie: String, schlagworte: List[String])
val sortiment = List(
Artikel("Wanderschuh", "Schuhe", List("outdoor", "leder")),
Artikel("Regenjacke", "Jacken", List("outdoor", "wasserdicht")),
Artikel("Laufschuh", "Schuhe", List("sport"))
)
sortiment.groupBy(_.kategorie)
// Map(Schuhe -> List(Wanderschuh…, Laufschuh…), Jacken -> List(Regenjacke…))
sortiment.map(_.schlagworte).flatten.distinct // List(outdoor, leder, wasserdicht, sport)
sortiment.flatMap(_.schlagworte).distinct // dasselbe mit flatMapflatMap wendet eine Funktion an, die selbst eine Sammlung liefert, und hängt alle Ergebnisse aneinander. Weitere nützliche Methoden: zip und zipWithIndex (siehe Tupel), sliding(2) für benachbarte Paare, grouped(3) für Blöcke, takeWhile und dropWhile, span, last und init.
Zugriff per Index – mit Vorsicht
xs(3) liefert das vierte Element, aber dafür muss die Liste von vorn durchlaufen werden. Bei großen Listen und häufigem Zugriff per Index ist das langsam.
| Operation | List | Vector |
|---|---|---|
| vorne anhängen | sehr schnell | schnell |
| hinten anhängen | langsam (ganze Liste kopieren) | schnell |
| Zugriff per Index | langsam (linear) | schnell (nahezu konstant) |
| head / tail | sehr schnell | schnell |
Vector ist deshalb die bessere Wahl, wenn Sie Elemente hinten anhängen oder oft per Index zugreifen. Die API ist dieselbe; oft genügt es, List(…) durch Vector(…) zu ersetzen. Mehr zur Auswahl in der Übersicht der Collections.
Veränderliche Listen
Brauchen Sie wirklich eine Liste, die Sie Stück für Stück aufbauen, gibt es scala.collection.mutable.ListBuffer. Am Ende wandeln Sie ihn mit toList in eine unveränderliche Liste um – Details in der Lektion über Array und veränderliche Collections.