scalatutorial.de

Stufe 5Collections · Lektion 25 von 34

List und ihre Funktionen

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

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

 Scala 3
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 unveränderliche Liste als Kette von Zellen bis Nil1head | tail2head | tail3head | tailNilval xs = 1 :: 2 :: 3 :: Nil0 :: xs hängt vorne an und teilt sich den Rest mit xs.
Eine Liste ist eine Kette von Zellen, die bei Nil endet.

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:

 Scala 3
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

 Scala 3
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.reverse

Keine dieser Methoden verändert preise – jede liefert eine neue Liste oder einen neuen Wert.

Zusammenfassen: fold und reduce

 Scala 3
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, lesbarer

foldLeft 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

 Scala 3
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 flatMap

flatMap 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.

OperationListVector
vorne anhängensehr schnellschnell
hinten anhängenlangsam (ganze Liste kopieren)schnell
Zugriff per Indexlangsam (linear)schnell (nahezu konstant)
head / tailsehr schnellschnell

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.

Passt dazu