scalatutorial.de

Stufe 2Sprachkern · Lektion 12 von 34

Tupel

Aktualisiert am 24. September 20263 Min. LesezeitSchritt 7/7 dieser Stufe

Ein Tupel fasst eine feste Anzahl von Werten unterschiedlichen Typs zusammen – ohne dass Sie dafür eine Klasse schreiben müssen. Ideal, wenn eine Funktion zwei Dinge zurückgeben soll oder Daten nur kurz gebündelt werden.

Tupel erzeugen

 Scala 3
val person = ("Ada", 1815, true)
// Typ: (String, Int, Boolean)

Runde Klammern, Kommas dazwischen – fertig. Der Typ eines Tupels besteht aus den Typen seiner Elemente in derselben Reihenfolge. (String, Int) und (Int, String) sind also verschiedene Typen.

Für Paare gibt es eine zweite Schreibweise mit Pfeil, die besonders bei Maps üblich ist:

 Scala 3
val paar = "de" -> "Deutschland"   // entspricht ("de", "Deutschland")

Auf Elemente zugreifen

 Scala 3
person._1    // "Ada"
person._2    // 1815
person(2)    // true – Index ab 0, neu in Scala 3

Die Namen _1, _2 und so weiter zählen ab eins. Scala 3 erlaubt zusätzlich den Zugriff über einen Index ab null wie bei einer Liste. Der Typ bleibt dabei genau bekannt: person(1) hat den Typ Int, nicht Any.

Zerlegen

Lesbarer als _1 und _2 ist es meist, ein Tupel direkt in benannte Werte zu zerlegen:

 Scala 3
val (name, jahr, _) = person
println(s"$name, geboren $jahr")

Der Unterstrich ignoriert ein Element, das Sie nicht brauchen. Dasselbe funktioniert in Pattern Matching, in for-Ausdrücken und bei Lambdas:

 Scala 3
val punkte = List(("Ada", 92), ("Alan", 85), ("Grace", 97))

punkte.foreach((name, p) => println(s"$name: $p"))   // Scala 3: Tupel automatisch zerlegt
val beste = punkte.maxBy(_._2)                          // ("Grace", 97)

punkte match
  case (erster, _) :: _ => println(s"Erster Eintrag: $erster")
  case Nil              => println("leer")

In Scala 2 musste man für das Zerlegen im Lambda noch { case (name, p) => … } schreiben; das funktioniert weiterhin.

Tupel als Rückgabewert

Der häufigste Einsatz: Eine Funktion liefert mehrere Ergebnisse.

 Scala 3
def minMax(xs: List[Int]): (Int, Int) =
  (xs.min, xs.max)

val (kleinste, groesste) = minMax(List(7, 2, 9, 4))

Auch die Standardbibliothek macht davon Gebrauch:

 Scala 3
val (gerade, ungerade) = (1 to 10).partition(_ % 2 == 0)
val (vorne, hinten)    = List(1, 2, 3, 4, 5).splitAt(2)
val (quotient, rest)   = (17 / 5, 17 % 5)

zip und unzip

zip verbindet zwei Sammlungen elementweise zu einer Sammlung von Paaren, unzip trennt sie wieder:

 Scala 3
val namen  = List("Ada", "Alan", "Grace")
val jahre  = List(1815, 1912, 1906)

val paare = namen.zip(jahre)
// List((Ada,1815), (Alan,1912), (Grace,1906))

val (n, j) = paare.unzip

namen.zipWithIndex
// List((Ada,0), (Alan,1), (Grace,2))

Sind die Sammlungen unterschiedlich lang, schneidet zip auf die kürzere ab.

Tupel mit einem Trick: Muster in match

Weil sich Tupel so leicht bilden lassen, eignen sie sich gut, um mehrere Bedingungen gleichzeitig zu prüfen:

 Scala 3
def fizzBuzz(n: Int): String = (n % 3, n % 5) match
  case (0, 0) => "FizzBuzz"
  case (0, _) => "Fizz"
  case (_, 0) => "Buzz"
  case _      => n.toString

Grenzen: wann eine case class besser ist

Tupel sind bequem, aber namenlos. Spätestens wenn ein Tupel mehr als zwei oder drei Elemente hat oder durch mehrere Methoden gereicht wird, leidet die Lesbarkeit: Was war noch mal _3? Dann ist eine case class die bessere Wahl:

 Scala 3
case class Messung(ort: String, temperatur: Double, zeit: java.time.Instant)

Sie kostet eine Zeile mehr und macht jeden Zugriff selbsterklärend: m.temperatur statt m._2.

Passt dazu