for Schleifen
In [38] beschreiben Martin Odersky et al. die Scala
for Schleife als das Scala Schweizer Armeemesser der Iteration.
"Scala's for expression is a Swiss army knife of Iteration."
Die Scala for Schleife eignet sich nicht nur zum Durchzählen
von integralen Werten. Mit Scala for Schleifen ist es möglich,
elegant über die Elemente einer Collection zu iterieren.
Dieses Kapitel soll die Möglichkeiten der for Schleife in Scala
näher beleuchten.
Die for - Schleife wird oft dazu verwendet (auch in anderen Programmiersprachen)
eine Int - Variable einen bestimmten Wertebereich durchlaufen zu lassen.
Das nachfolgende Beispiel zeigt eine for - Schleife, wo eine Variable
i die Werte 0 bis 4 annimmt und der aktuelle Wert ausgegeben wird.
for (i <- 0 to 4)
println(i)
Der Ablauf dieser Schleife führt zu folgender Ausgabe:
0
1
2
3
4
Möchte man, dass der letzte Wert nicht Bestandteil der Schleife ist,
kann man folgende Variante der for - Schleife verwenden:
for (i <- 0 until 4)
println(i)
Der Ablauf dieser Schleife führt zu folgender Ausgabe:
0
1
2
3
Die oben vorgestellten for - Schleifen können den Eindruck
hinterlassen, dass es sich bei der Variable i um eine var
Variable handelt, da der Wert bei jedem Schleifendurchlauf verändert wird.
Dies ist jedoch nicht der Fall. Bei jedem Schleifendurchlauf wird eine
neue val Variable für i verwendet. Eine
Zuweisung eines neuen Wertes für i ist innerhalb der
Schleife nicht möglich.
Der Ausdruck in der hier gezeigten for - Schleife
wird als Generator (eng. generator) bezeichnet.
<- generiert individuelle Werte aus einer Collection
zur Verwendung in einem Ausdruck 1.
Rechts von <- steht die Collection, aus der die Werte
entnommen werden sollen. Links von <- steht der Name einer
val Variablen, auf die wir innerhalb der Schleife zugreifen können.
Wenn also <- Elemente aus einer Collection generiert,
müssen auch die Ausdrücke 0 to 4 und 0 until 4
als Ergebnis eine Collection liefern, was auch der Fall ist. Die beiden Ausdrücke liefern
eine Collection vom Typ scala.collection.immutable.Range (bzw.
scala.collection.immutable.Range.Inclusive bei to),
welche Int- Elemente in einem Bereich zwischen einem Start- und Endwert
bereitstellt. Der Umstand, dass eine Collection in der for-Schleife
verwendet wird, führt dazu, dass wir die Collection auch außerhalb
des for Ausdruckes definieren können und mithilfe einer Variablen
der for Schleife zuführen können.
scala> val myRange = 1 to 3 myRange: scala.collection.immutable.Range.Inclusive = Range(1, 2, 3) scala> for (i <- myRange) println(i) 1 2 3
In den bisherigen Beispielen haben wir Int Literale verwendet. Wenn
wir uns die API Dokumentation ansehen, werden wir feststellen, dass für
Int die Funktionen to und until nicht definiert sind.
Damit die Funktionen zur Verfügung stehen, wird der Int Wert
zunächst (durch impliziete Typumwandlung) in einen scala.runtime.RichInt
Typ umgewandelt, für den die Funktionen definiert sind. Die impliziete Typkonvertierung
ist in scala.LowPriorityImplicits definiert, welche durch scala.Prefed
erweitert wird.
In for - Schleifen, die wir bisher kennengelernt haben, muss nicht
unbedingt eine Collection vom Typ Range verwendet werden. Wir können auch
problemlos dem Generator eine Collection vom Typ List zur Verarbeitung
übergeben.
scala> val myList = List(1,2,3) myList: List[Int] = List(1, 2, 3) scala> for (i <- myList) println(i) 1 2 3
In den bisherigen Schleifen sind wir Int - Werte immer mit einem Inkrement von 1 durchlaufen.
Die beiden Methoden to und until sind in RichInt überladen,
sodass wir das Inkrement festlegen können.
def to (end: Int, step: Int): Inclusive
def to (end: Int): Inclusive
def until (end: Int, step: Int): Range
def until (end: Int): Range
Das nachfolgende Beispiel zeigt eine for Schleife, die Int - Werte mit einem Inkrement von 3
durchläuft.
scala> for (i <- 4 to (17,3)) println(i) 4 7 10 13 16
Die hier gezeigte Vorgehensweise gilt Analog für die Methode until.
Die Int - Werte müssen nicht in aufsteigender Reihenfolge durchlaufen werden,
vielmehr können wir auch ein Dekrement (die Werte werden kleiner) als Schrittweite festlegen.
scala> for (i <- 13 until (6,-2)) println(i) 13 11 9 7
Bisher haben wir nur for-Schleifen mit Int-Werten verwendet.
Wir können jedoch auch Collections anderer (aller) Typen verwenden.
Beispielhaft folgt nachfolgend eine Anwendung der for-Schleife
auf eine List mit Strings.
scala> val myList = List("Hello","world","Scala","rocks")
myList: List[java.lang.String] = List(Hello, world, Scala, rocks)
scala> for (s <- myList) println(s)
Hello
world
Scala
rocks
In vielen Fällen möchten wir verschieden Werte verschachtelt durchlaufen.
In Scala können wir die Verschachtelung in einer for-Schleife definieren.
Beispielsweise können wir zwei Generatoren in einer for-Schleife definieren,
die dann verschachtelt durchlaufen werden.
scala> for (i <- 0 to 2; j <- 3 to 4) println(i+" "+j) 0 3 0 4 1 3 1 4 2 3 2 4
Verwenden wir statt der runden Klammern geschweifte Klammern und definieren die Generatoren in zwei Zeilen, können wir auf die Trennung mit einem Semikolon verzichten.
scala> for {i <- 0 to 2
| j <- 3 to 4} println(i+" "+j)
0 3
0 4
1 3
1 4
2 3
2 4
Nicht immer möchten wir alle Werte einer Collection in einer for-Schreife
verarbeiten. Zu diesem Zwecke stehen uns in for-Schleifen sogenannte
Filter zur Verfügung. Ein Filter wird dabei einfach durch einen if-Ausdruck
innerhalb des for-Ausdrucks definiert. Liefert der Filter das Ergebnis true
wird der aktuelle Schleifendurchlauf bearbeitet, liefert der Filter false wird
der aktuelle Schleifendurchlauf nicht bearbeitet.
Im nachfolgenden Beispiel wenden wir eine for Schleife auf einen Range
mit den Werten 1 bis 10 (inklusive) an und geben nur die geraden Werte auf der Systemausgabe aus.
object ForLoop {
def main(args: Array[String]): Unit = {
for {i <- 1 to 10
if (i % 2 == 0)} println(i)
}
}
Die Ausführung des Programms führt zu folgender Ausgabe auf der Systemausgabe:
2 4 6 8 10
Zugegeben, das gleiche hätten wir auch mit einer entsprechenden Range-Definition
erreichen können, was aber nicht Inhalt des Abschnittes ist.
Wie bei den Generatoren sind wir bei Filter nicht auf einen einzigen if-Ausdruck beschränkt
und können mehrere Filter definieren. Im nachfolgenden Beispiel verwenden wir zwei Generatoren
und zwei Filter. Zur Ausführung des Schleifeninhaltes kommt es im Beispiel nur, wenn die
Variable i gerade und die Variable j ungerade ist.
object ForLoop2 {
def main(args: Array[String]): Unit = {
for {i <- 1 to 6
if (i % 2 == 0)
j <- 1 to 6
if (j % 2 != 0)} println(i+" "+j)
}
}
Die Ausführung des Programms führt zu folgender Ausgabe auf der Systemausgabe:
2 1 2 3 2 5 4 1 4 3 4 5 6 1 6 3 6 5
Die Filter haben keine bestimmte Position innerhalb des for-Ausdrucks.
Greifen Filter aber auf Variable zu, die im for-Ausdruck definiert werden,
müssen diese nach der Variablendefinition positioniert werden. Dementsprechend
hätten wir unser obiges Beispiel auch folgendermaßen schreiben können.
object ForLoop2 {
def main(args: Array[String]): Unit = {
for {i <- 1 to 6
j <- 1 to 6
if (i % 2 == 0)
if (j % 2 != 0)} println(i+" "+j)
}
}