Motivation
Beim Einsatz der for-Schleife, weiß man im Prinzip beim Schreiben eines Programms, wie oft diese Schleife durchlaufen werden muss. Allerdings gibt es viele Probleme, die sich zwar mit Programmen lösen lassen, bei denen aber zu Beginn nicht klar ist, wie oft bestimmte Schritte wiederholt werden müssen.
Ein einfaches Beispiel ist das Auffinden von Teilern einer Zahl: Manche Zahlen haben nur 2 Teiler (Primzahlen), andere 5 oder 30, etc.
In solchen Fällen kann die Anzahl der Wiederholungen nicht einfach beim Schreiben eines Programms angegeben werden. Ein solches Problem kann aber über eine Wahrheitsbedingung gelöst werden, indem das Programm so lange läuft, wie eine Bedingung erfüllt ist.
Genau für diesen Zweck existiert die while-Schleife.
Syntax
Grundsyntax
Betrachten wir zunächst ein einfaches Beispiel. Es sollen solange zufällige Zahlen ausgegeben bis eine Zahl größer als 0.5 ist. Dies könnte mit Hilfe der while-Schleife folgendermaßen bewerkstelligt werden:
double zufall = Math.random(); (1)
while (zufall < 0.5) { (2)
System.out.println(zufall);
zufall = Math.random(); (3)
}
System.out.println("Fertig"); (4)
1 |
Eine Variable zur Speicherung von Kommazahlen namens zufall wird angelegt und mit einer Zufallszahl zwischen 0 und 1 belegt. |
2 |
Solange diese Zufallszahl kleiner als 0.5 ist, wird diese Zahl ausgegeben und |
3 |
eine neue Zufallszahl in zufall gespeichert. |
4 |
Wenn die Bedingung der while-Schleife nicht mehr erfüllt ist, fährt das Programm hinter dem while-Block mit der Ausführung fort, gibt hier also "Fertig" aus. |
|
Beachte, dass die Bedingung in den Klammern nach dem while -Schlüsselwort aus der While-Schleife heraus im Normalfall veränderbar sein sollte, da sonst die Gefahr einer Endlosschleife besteht. In unserem Beispiel findet eine solche Änderung statt, da der Wert von zufall in jedem Durchlauf zufällig neu bestimmt wird und die Wiederholung der Schleife somit irgendwann abgebrochen wird, da ein zufälliger Wert >= 0.5 in zufall gespeichert wird.
|
Variante: die do-while-Schleife
Eng verwandt mit der while-Schleife ist die do-while-Schleife: bei dieser wird die Wiederholungsbedingung erst nach der Durchführung der zu wiederholenden Ausdrücke überprüft, was bedeutet, dass diese Schleife immer mindestens ein Mal durchlaufen wird.
double zufall = Math.random();
do { (1)
System.out.println(zufall);
zufall = Math.random();
} while (zufall < 0.5); (2)
System.out.println("Fertig"); (3)
1 |
Starte den zu wiederholenden Teil: ein Mal wird der Bereich zwischen den geschweiften Klammern mindestens durchlaufen. |
2 |
Die Bedingung für die Wiederholungen wird hier erst nach der ersten Durchführung überprüft. |
3 |
Hierher gelangt die Programmausführung erst, wenn die Bedingung der do-while-Schleife nicht mehr erfüllt ist. |
Der Unterschied zum Programm mit der normalen while-Schleife, wird dieses Programm immer mindestens eine Zahl ausgeben, auch wenn sie größer als 0,5 sein sollte.
Im folgenden wird aber fast ausschließlich mit der while-Schleife gearbeitet, da alle Anwendungsprobleme auch mit dieser zu lösen sind.
Beispiel: Collatz’sche Vermutung
Grundproblem
Stellen wir uns eine Art Spiel vor: Man denke sich eine natürliche Zahl ungleich der 1.
Handelt es sich dabei um eine gerade Zahl, so dividieren wir sie durch 2.
Ist die Zahl jedoch ungerade, so multiplizieren wir sie mit 3 und addieren 1, also kurz:
z gerade ⇒zneu=2z,
z ungerade ⇒zneu=3⋅z+1
Mit der neuen Zahl wiederholt man dieses "Spiel" und verfährt solange nach dieser Regel, bis man für z die Zahl 1 erhält.
Die Collatz’sche Vermutung besagt nun, dass man, unabhängig von der Startzahl, immer bei 1 landet. Vermutung, da man bis heute keinen mathematischen Beweis dafür hat, dass dies tatsächlich immer gelingt.
Dieses Problem ist gut für eine While-Schleife geeignet, da man zum Zeitpunkt des Programmierens noch nicht weiß, wie oft das Spiel durchlaufen werden muss. Man weiß nur, dass es läuft, solange z>1 ist. Und immer, wenn das Wort "solange" einen Wiederholungsvorgang beschreibt, ist die While-Schleife ein guter Kandidat zur Lösung des Problems mit Hilfe eines Algorithmus.
Eine mögliche Lösung könnte so aussehen:
int z=5; (1)
System.out.println("Startwert: "+z);
while (z>1){ (2)
if (z%2==0){ (3)
z/=2; (4)
} else {
z=3*z+1;
}
System.out.println(z);
}
1 |
Die Ganzzahlvariable z enthält die betrachtete Zahl |
2 |
Der folgende Prozess wird wiederholt, solange z>1 ist |
3 |
z%2 (sprich: "z modulo 2") liefert den Rest der Division von z durch 2: ist z also gerade, erhält man 0, sonst 1 |
4 |
z/=2 ist die abkürzende Schreibweise für z=z/2 |
Nach dem Starten liefert das Programm die Ausgabe:
Erweiterung 1: Zählen der Schritte bis zur 1
Möchte man zählen, wie viele Schritte benötigt werden, bis man zur 1 gelangt, so muss man einen Zähler einbauen. Dies gelingt beispielsweise folgendermaßen:
int z=5;
int zaehler=0; (1)
System.out.println("Startwert: "+z);
while (z>1){
if (z%2==0){
z/=2;
} else {
z=3*z+1;
}
zaehler++; (2)
System.out.println(z);
}
System.out.println("Anzahl der Schritte: "+zaehler); (3)
1 |
Deklaration und Initialisierung der Zählvariable zaehler |
2 |
An diese Stelle kommt das Programm jedes Mal, wenn z verändert wird. Somit ist das die perfekte Stelle, um die Anzahl der Schritte zu zählen |
3 |
An diese Stelle kommt das Programm erst nach dem Beenden der While-Schleife. Somit kann hier nun die Anzahl der Schritte ausgegeben werden. |
Erweiterung 2: Ausgeben der Schritte und Schrittzahlen für alle Zahlen von 2 bis 10
Nun soll nicht nur eine Zahl, sondern mehrere untersucht werden, was man intuitiv zunächst so umsetzen könnte:
for (int z = 2; z<=10; z++) {(1)
int zaehler = 0;
System.out.println("Startwert: " + z);
while (z > 1) {
if (z % 2 == 0) {
z /= 2;
} else {
z = 3 * z + 1;
}
zaehler++;
System.out.print(z + " ");(2)
}
System.out.println(", Anzahl der Schritte: " + zaehler);
}
1 |
Anstatt z auf einen festen Wert zu setzen, wird z nun über die for-Schleife festgelegt. |
2 |
Statt println verwenden wir hier nur print , wodurch nach der Ausgabe keine neue Zeile bgeonnen wird. Das Leerzeichen hinter der Ausgabe von z wird eingefügt, damit die Ausgabe schöner formatiert wird. |
Startet man das Programm, so erhält man überraschenderweise nicht das gewünschte Ergebnis, sondern immer wieder die gleiche Ausgabe
...
Startwert: 2
1 , Anzahl der Schritte: 1
Startwert: 2
1 , Anzahl der Schritte: 1
Startwert: 2
1 , Anzahl der Schritte: 1
Startwert: 2
1 , Anzahl der Schritte: 1
...
Was haben wir falsch gemacht?
Innerhalb der while-Schleife wird der Wert von z
verändert und ist nach Beendigung der while-Schleife immer 1.
Da z
aber gleichzeitig steuert, wie oft die for-Schleife durchlaufen wird, haben wir hier ein Problem: die for-Schleife läuft solange, wie z < = 10
ist und da z durch die while-Schleife immer wieder auf 1 gesetzt wird, erhöht die for-Schleife den Wert um 1 (also auf 2) und beginnt das Spiel von vorne.
Letzten Endes haben wir hier eine Endlosschleife erzeugt.
Abhilfe schafft das Einführen einer neuen Variable innerhalb der for-Schleife, wie folgt:
for (int i = 2; i<=10; i++) {(1)
int z=i;(2)
int zaehler = 0;
System.out.println("Startwert: " + z);
while (z > 1) {
if (z % 2 == 0) {
z /= 2;
} else {
z = 3 * z + 1;
}
zaehler++;
System.out.print(z + " ");
}
System.out.println(", Anzahl der Schritte: " + zaehler);
}
1 |
Benutze als Laufvariable i statt z |
2 |
Übergabe von i an z : dadurch hat die Änderung von z keinen Einfluss auf das Laufverhalten der Schleife |
Das z
wird innerhalb der while-Schleife nach wie vor jedes Mal auf 1 abgeändert, was allerdings keinen Einfluss auf das i
hat, das für die Steuerung der Schleife verantwortlich ist.
Nun liefert das Programm die Ausgabe
Startwert: 2
1 , Anzahl der Schritte: 1
Startwert: 3
10 5 16 8 4 2 1 , Anzahl der Schritte: 7
Startwert: 4
2 1 , Anzahl der Schritte: 2
Startwert: 5
16 8 4 2 1 , Anzahl der Schritte: 5
Startwert: 6
3 10 5 16 8 4 2 1 , Anzahl der Schritte: 8
Startwert: 7
22 11 34 17 52 26 13 40 20 10 5 16 8 4 2 1 , Anzahl der Schritte: 16
Startwert: 8
4 2 1 , Anzahl der Schritte: 3
Startwert: 9
28 14 7 22 11 34 17 52 26 13 40 20 10 5 16 8 4 2 1 , Anzahl der Schritte: 19
Startwert: 10
5 16 8 4 2 1 , Anzahl der Schritte: 6
Erweiterung 3: Collatz mit "grafischer Ausgabe"
Eine letzte Erweiterung soll mit Hilfe von #
-Symbolen die Anzahl der notwendigen Schritte anzeigen, um besser zu visualisieren, wo viele oder wenige Schritte benötigt werden.
for (int i = 2; i <=10; i++) {
int z=i;
int zaehler = 0;
System.out.println("Startwert: " + z);
while (z > 1) {
if (z % 2 == 0) {
z /= 2;
} else {
z = 3 * z + 1;
}
zaehler++;
System.out.print(z + " ");
}
System.out.println("");
for (int j=0;j<zaehler;j++){ (1)
System.out.print("#"); (2)
}
System.out.println("\n");(3)
}
1 |
Neue Schleife mit so vielen Durchläufen, wie zaehler gezählt hat. Hier wird eine neue Laufvariable j eingeführt, da wir uns noch in der for-Schleife mit der Laufvariable i befinden. |
2 |
Wieder print statt println , damit nicht nach jedem # eine neue Zeile beginnt |
3 |
println beendet mit seiner Ausgabe die aktuelle Zeile und springt in die nächste. Das Gleiche erreicht man mit \n : Kommt dieses in einem String vor, steht dies für eine neue Zeile. Durch die Kombination aus beidem, wie hier, springt der Cursor sogar in die übernächste Zeile, so dass jeweils eine Leerzeile entsteht. |
Die Ausgabe sieht dann so aus:
Startwert: 2
1
#
Startwert: 3
10 5 16 8 4 2 1
#######
Startwert: 4
2 1
##
Startwert: 5
16 8 4 2 1
#####
Startwert: 6
3 10 5 16 8 4 2 1
########
Startwert: 7
22 11 34 17 52 26 13 40 20 10 5 16 8 4 2 1
################
Startwert: 8
4 2 1
###
Startwert: 9
28 14 7 22 11 34 17 52 26 13 40 20 10 5 16 8 4 2 1
###################
Startwert: 10
5 16 8 4 2 1
######
== Motivation
Beim Einsatz der _for-Schleife_, weiß man im Prinzip beim Schreiben eines Programms, wie oft diese Schleife durchlaufen werden muss. Allerdings gibt es viele Probleme, die sich zwar mit Programmen lösen lassen, bei denen aber zu Beginn nicht klar ist, wie oft bestimmte Schritte wiederholt werden müssen.
Ein einfaches Beispiel ist das Auffinden von Teilern einer Zahl: Manche Zahlen haben nur 2 Teiler (Primzahlen), andere 5 oder 30, etc.
In solchen Fällen kann die Anzahl der Wiederholungen nicht einfach beim Schreiben eines Programms angegeben werden. Ein solches Problem kann aber über eine Wahrheitsbedingung gelöst werden, indem das Programm so lange läuft, wie eine Bedingung erfüllt ist.
Genau für diesen Zweck existiert die *while-Schleife*.
== Syntax
=== Grundsyntax
Betrachten wir zunächst ein einfaches Beispiel. Es sollen solange zufällige Zahlen ausgegeben bis eine Zahl größer als 0.5 ist. Dies könnte mit Hilfe der while-Schleife folgendermaßen bewerkstelligt werden:
[ indent=0]
----
double zufall = Math.random(); //<1>
while (zufall < 0.5) { // <2>
System.out.println(zufall);
zufall = Math.random(); // <3>
}
System.out.println("Fertig"); // <4>
----
<1> Eine Variable zur Speicherung von Kommazahlen namens `zufall` wird angelegt und mit einer Zufallszahl zwischen 0 und 1 belegt.
<2> Solange diese Zufallszahl kleiner als 0.5 ist, wird diese Zahl ausgegeben und
<3> eine neue Zufallszahl in `zufall` gespeichert.
<4> Wenn die Bedingung der while-Schleife nicht mehr erfüllt ist, fährt das Programm hinter dem while-Block mit der Ausführung fort, gibt hier also "Fertig" aus.
[NOTE]
====
Beachte, dass die Bedingung in den Klammern nach dem `while`-Schlüsselwort aus der While-Schleife heraus im Normalfall veränderbar sein sollte, da sonst die Gefahr einer *Endlosschleife* besteht. In unserem Beispiel findet eine solche Änderung statt, da der Wert von `zufall` in jedem Durchlauf zufällig neu bestimmt wird und die Wiederholung der Schleife somit irgendwann abgebrochen wird, da ein zufälliger Wert >= 0.5 in `zufall` gespeichert wird.
====
=== Variante: die do-while-Schleife
Eng verwandt mit der while-Schleife ist die *do-while-Schleife*: bei dieser wird die Wiederholungsbedingung erst nach der Durchführung der zu wiederholenden Ausdrücke überprüft, was bedeutet, dass diese Schleife immer mindestens ein Mal durchlaufen wird.
[ indent=0]
----
double zufall = Math.random();
do { // <1>
System.out.println(zufall);
zufall = Math.random();
} while (zufall < 0.5); // <2>
System.out.println("Fertig"); // <3>
----
<1> Starte den zu wiederholenden Teil: ein Mal wird der Bereich zwischen den geschweiften Klammern mindestens durchlaufen.
<2> Die Bedingung für die Wiederholungen wird hier erst _nach_ der ersten Durchführung überprüft.
<3> Hierher gelangt die Programmausführung erst, wenn die Bedingung der do-while-Schleife nicht mehr erfüllt ist.
Der Unterschied zum Programm mit der normalen while-Schleife, wird dieses Programm immer mindestens eine Zahl ausgeben, auch wenn sie größer als 0,5 sein sollte.
Im folgenden wird aber fast ausschließlich mit der while-Schleife gearbeitet, da alle Anwendungsprobleme auch mit dieser zu lösen sind.
== Beispiel: Collatz'sche Vermutung
=== Grundproblem
Stellen wir uns eine Art Spiel vor: Man denke sich eine natürliche Zahl ungleich der 1.
Handelt es sich dabei um eine _gerade_ Zahl, so dividieren wir sie durch 2.
Ist die Zahl jedoch _ungerade_, so multiplizieren wir sie mit 3 und addieren 1, also kurz:
stem:[z text( gerade ) => z_text(neu)=z/2],
stem:[z text( ungerade ) => z_text(neu)=3*z+1]
Mit der neuen Zahl wiederholt man dieses "Spiel" und verfährt solange nach dieser Regel, bis man für z die Zahl 1 erhält.
Die *Collatz'sche Vermutung* besagt nun, dass man, unabhängig von der Startzahl, immer bei 1 landet. _Vermutung_, da man bis heute keinen mathematischen Beweis dafür hat, dass dies tatsächlich immer gelingt.
Dieses Problem ist gut für eine While-Schleife geeignet, da man zum Zeitpunkt des Programmierens noch nicht weiß, wie oft das Spiel durchlaufen werden muss. Man weiß nur, dass es läuft, _solange_ z>1 ist. Und immer, wenn das Wort "solange" einen Wiederholungsvorgang beschreibt, ist die While-Schleife ein guter Kandidat zur Lösung des Problems mit Hilfe eines Algorithmus.
Eine mögliche Lösung könnte so aussehen:
[ indent=0]
----
int z=5; // <1>
System.out.println("Startwert: "+z);
while (z>1){ // <2>
if (z%2==0){ // <3>
z/=2; //<4>
} else {
z=3*z+1;
}
System.out.println(z);
}
----
<1> Die Ganzzahlvariable z enthält die betrachtete Zahl
<2> Der folgende Prozess wird wiederholt, _solange_ z>1 ist
<3> `z%2` (sprich: "z modulo 2") liefert den Rest der Division von z durch 2: ist z also gerade, erhält man 0, sonst 1
<4> `z/=2` ist die abkürzende Schreibweise für `z=z/2`
Nach dem Starten liefert das Programm die Ausgabe:
....
Startwert: 5
16
8
4
2
1
....
=== Erweiterung 1: Zählen der Schritte bis zur 1
Möchte man zählen, wie viele Schritte benötigt werden, bis man zur 1 gelangt, so muss man einen Zähler einbauen. Dies gelingt beispielsweise folgendermaßen:
[indent=0]
----
int z=5;
int zaehler=0; // <1>
System.out.println("Startwert: "+z);
while (z>1){
if (z%2==0){
z/=2;
} else {
z=3*z+1;
}
zaehler++; //<2>
System.out.println(z);
}
System.out.println("Anzahl der Schritte: "+zaehler); //<3>
----
<1> Deklaration und Initialisierung der Zählvariable `zaehler`
<2> An diese Stelle kommt das Programm jedes Mal, wenn z verändert wird. Somit ist das die perfekte Stelle, um die Anzahl der Schritte zu zählen
<3> An diese Stelle kommt das Programm erst _nach_ dem Beenden der While-Schleife. Somit kann hier nun die Anzahl der Schritte ausgegeben werden.
=== Erweiterung 2: Ausgeben der Schritte und Schrittzahlen für alle Zahlen von 2 bis 10
Nun soll nicht nur eine Zahl, sondern mehrere untersucht werden, was man intuitiv zunächst so umsetzen könnte:
[indent=0]
----
for (int z = 2; z<=10; z++) {//<1>
int zaehler = 0;
System.out.println("Startwert: " + z);
while (z > 1) {
if (z % 2 == 0) {
z /= 2;
} else {
z = 3 * z + 1;
}
zaehler++;
System.out.print(z + " ");//<2>
}
System.out.println(", Anzahl der Schritte: " + zaehler);
}
----
<1> Anstatt `z` auf einen festen Wert zu setzen, wird `z` nun über die for-Schleife festgelegt.
<2> Statt `println` verwenden wir hier nur `print`, wodurch nach der Ausgabe _keine_ neue Zeile bgeonnen wird. Das Leerzeichen hinter der Ausgabe von `z` wird eingefügt, damit die Ausgabe schöner formatiert wird.
Startet man das Programm, so erhält man überraschenderweise _nicht_ das gewünschte Ergebnis, sondern immer wieder die gleiche Ausgabe
....
...
Startwert: 2
1 , Anzahl der Schritte: 1
Startwert: 2
1 , Anzahl der Schritte: 1
Startwert: 2
1 , Anzahl der Schritte: 1
Startwert: 2
1 , Anzahl der Schritte: 1
...
....
Was haben wir falsch gemacht?
Innerhalb der while-Schleife wird der Wert von `z` verändert und ist nach Beendigung der while-Schleife immer 1.
Da `z` aber gleichzeitig steuert, wie oft die for-Schleife durchlaufen wird, haben wir hier ein Problem: die for-Schleife läuft solange, wie `z < = 10` ist und da z durch die while-Schleife immer wieder auf 1 gesetzt wird, erhöht die for-Schleife den Wert um 1 (also auf 2) und beginnt das Spiel von vorne.
Letzten Endes haben wir hier eine *Endlosschleife* erzeugt.
Abhilfe schafft das Einführen einer neuen Variable innerhalb der for-Schleife, wie folgt:
[indent=0]
----
for (int i = 2; i<=10; i++) {//<1>
int z=i;//<2>
int zaehler = 0;
System.out.println("Startwert: " + z);
while (z > 1) {
if (z % 2 == 0) {
z /= 2;
} else {
z = 3 * z + 1;
}
zaehler++;
System.out.print(z + " ");
}
System.out.println(", Anzahl der Schritte: " + zaehler);
}
----
<1> Benutze als Laufvariable `i` statt `z`
<2> Übergabe von `i` an `z`: dadurch hat die Änderung von `z` keinen Einfluss auf das Laufverhalten der Schleife
Das `z` wird innerhalb der while-Schleife nach wie vor jedes Mal auf 1 abgeändert, was allerdings keinen Einfluss auf das `i` hat, das für die Steuerung der Schleife verantwortlich ist.
Nun liefert das Programm die Ausgabe
....
Startwert: 2
1 , Anzahl der Schritte: 1
Startwert: 3
10 5 16 8 4 2 1 , Anzahl der Schritte: 7
Startwert: 4
2 1 , Anzahl der Schritte: 2
Startwert: 5
16 8 4 2 1 , Anzahl der Schritte: 5
Startwert: 6
3 10 5 16 8 4 2 1 , Anzahl der Schritte: 8
Startwert: 7
22 11 34 17 52 26 13 40 20 10 5 16 8 4 2 1 , Anzahl der Schritte: 16
Startwert: 8
4 2 1 , Anzahl der Schritte: 3
Startwert: 9
28 14 7 22 11 34 17 52 26 13 40 20 10 5 16 8 4 2 1 , Anzahl der Schritte: 19
Startwert: 10
5 16 8 4 2 1 , Anzahl der Schritte: 6
....
=== Erweiterung 3: Collatz mit "grafischer Ausgabe"
Eine letzte Erweiterung soll mit Hilfe von `#` -Symbolen die Anzahl der notwendigen Schritte anzeigen, um besser zu visualisieren, wo viele oder wenige Schritte benötigt werden.
Eine Möglichkeit wäre:
[indent=0]
----
for (int i = 2; i <=10; i++) {
int z=i;
int zaehler = 0;
System.out.println("Startwert: " + z);
while (z > 1) {
if (z % 2 == 0) {
z /= 2;
} else {
z = 3 * z + 1;
}
zaehler++;
System.out.print(z + " ");
}
System.out.println("");
for (int j=0;j<zaehler;j++){ // <1>
System.out.print("#"); // <2>
}
System.out.println("\n");//<3>
}
----
<1> Neue Schleife mit so vielen Durchläufen, wie `zaehler` gezählt hat. Hier wird eine neue Laufvariable `j` eingeführt, da wir uns noch in der for-Schleife mit der Laufvariable `i` befinden.
<2> Wieder `print` statt `println`, damit nicht nach jedem `#` eine neue Zeile beginnt
<3> `println` beendet mit seiner Ausgabe die aktuelle Zeile und springt in die nächste. Das Gleiche erreicht man mit `\n`: Kommt dieses in einem String vor, steht dies für eine neue Zeile. Durch die Kombination aus beidem, wie hier, springt der Cursor sogar in die übernächste Zeile, so dass jeweils eine Leerzeile entsteht.
Die Ausgabe sieht dann so aus:
....
Startwert: 2
1
#
Startwert: 3
10 5 16 8 4 2 1
#######
Startwert: 4
2 1
##
Startwert: 5
16 8 4 2 1
#####
Startwert: 6
3 10 5 16 8 4 2 1
########
Startwert: 7
22 11 34 17 52 26 13 40 20 10 5 16 8 4 2 1
################
Startwert: 8
4 2 1
###
Startwert: 9
28 14 7 22 11 34 17 52 26 13 40 20 10 5 16 8 4 2 1
###################
Startwert: 10
5 16 8 4 2 1
######
....