Perl 6

Die Zukunft des Programmierens

Schleifen - Anweisungen wiederholen

Angenommen, Sie wollen (oder müssen) ein Programm schreiben, dass eine Zahl so oft verdoppelt, bis sie größer als 100 ist, und dann das Ergebnis ausgibt.

Mit dem bisherigen Wissen könnte das so aussehen:

use v6;

my $zahl = $*IN.get;

if $zahl > 100 {
	say $zahl;
} else {
	$zahl = $zahl * 2;
	if $zahl > 100 {
		say $zahl;
	} else {
		$zahl = $zahl * 2;
		if $zahl > 100 {
			say $zahl
		} else {
			...
		}
	}
}

Diese Lösung ist einfach nur hässlich: es ist viel Schreibaufwand, Fehleranfällig und, noch schlimmer: sie ist falsch. Denn egal wie häufig man diese Konstruktion verschachtelt, man kann sich nie sicher sein, dass man es oft genug hat (der Benutzer kann ja 0.00000001 eingeben, oder noch kleine Zahlen).

Perl hat natürlich eine bessere Lösung für dieses Problem: Schleifen:

use v6;
my $zahl = $*IN.get;

while $zahl <= 100 {
	$zahl = $zahl * 2;
}
say $zahl;
 

while prüft die Bedingung, die direkt hinter dem Wort while steht, und falls sie erfüllt ist, wird der Block dahinter ausgeführt. Dann wird die Bedingung wieder überprüft, wenn sie immer noch erfüllt ist, wird der Block wieder ausgeführt, und so weiter. Und zwar so lange, bis die Bedingung nicht mehr erfüllt ist.

Wenn der Benutzer z.B. 5 eingibt, wird beim ersten durchlaufen der Schleife überprüft, ob 5 <= 100 ist - ist es.

Dann wird $zahl auf 10 gesetzt.

Dann wird wieder überprüft, ob $zahl <= 100 ist. $zahl hat den Wert 10, die Bedingung ist immer noch erfüllt.

Deshalb wird $zahl = $zahl * 2 ausgeführt, dieses Mal mit dem Ergebnis 20.

Nach dem nächsten Durchlauf hat $zahl den Wert 40, dann 80, und dann 160.

Und wenn $zahl den Wert 160 hat, wird ist die Bedingung $zahl <= 100 nicht mehr erfüllt.

Die ewige Gefahr: Endlosschleifen

Was passiert, wenn der Benutzer "0" eingibt?

Die Bedingung 0 < 100 ist sicherlich erfüllt, also wird $zahl verdoppelt - und bleibt 0. Und nochmal. Und nochmal.

Diese Schleife wird nie abbrechen, wenn man sie mit $zahl == 0 oder mit negativen Zahlen startet - eine sogenannte Endlosschleife.

Zum Glück kann man das Programm leicht abbrechen, wenn man Steuerung+C drückt.

Und was tut man dagegen? Für manche Anwendungen reicht es sicher, ein if davor zu packen:

use v6;
my $zahl = $*IN.get;

if $zahl > 0 {

	while $zahl <= 100 {
		$zahl = $zahl * 2;
	}
	say $zahl;
} else {
	say "unmöglich";
}
 

Aber was tun, wenn es eine Anwendung ist, bei der man unbedingt eine positive Zahl braucht? Die Antwort ist: eine while-Schleife:

use v6;
my $zahl = $*IN.get;
while $zahl <= 0 {
	say "bitte POSITIVE Zahl eingeben:";
	$zahl = $*IN.get;
}

# hier ist $zahl auf jeden Fall > 0

while $zahl <= 100 {
	$zahl = $zahl * 2;
}
say $zahl;
 

Kleiner Exkurs: Schleifen vermeiden

Erstaunlich häufig kann man Schleifen vermeiden, die etwas berechnen, wenn man ein wenig Mathematik kann und in das Programm hineinsteckt. Der Vorteil ist, dass die Programme dann schneller laufen.

Für die mathematisch versierten Leser hier eine kurze Erklärung, wie man in obigem Programm die Schleife, die $zahl multipliziert, vermeiden kann. Wer es nicht versteht: macht nichts, einfach weiter unten wieder weiter lesen.

Die Idee ist folgende: man bildet den Quotienten aus 100 und $zahl, nennen wir ihn $q. Wenn man die nächst höhere Zweierpotenz kennt, kann man $zahl damit multiplizieren, und hat damit das Problem gelöst.

Wenn man z.B. 20 als Anfangszahl hat, ist der Quotient $q = 5. Die nächst höhere Zweierpotenz ist 8, also ist 20 * 8 == 160 unser Ergebnis.

Doch wie findet man die nächst höhere Zweierpotenz? Das geht, indem man den Logarithmus zur Basis zwei bildet, aufrundet und Zwei damit potenziert:

my $zahl = 12;
my $limit = 100;
my $q = $limit / $zahl;

# Den Zweierlogarithmus von $x kann man als log($x)/log(2) berechnen:
my $potenz =  log($q) / log(2);

#aufrunden:
$potenz = ceil $potenz;
my $ergebnis = $zahl * 2**$potenz;

Wenn man das ganze etwas kompakter schreiben will, geht das so:

my $zahl = 12;
my $limit = 100;
my $result = $zahl * 2**(ceil(log($limit/$zahl)/log(2));

Dadurch wird der Quellcode aber auch wesentlich schwerer lesbar. Wenn man so eine Zeile in seinen Programmen verwendet, sollte man ausführlich kommentieren, was der Code macht.

Andere Schleifen

Mit der vorgestellten while-Schleife kann man zwar schon alles machen, was man will, aber es gibt in Perl 6 noch weitere Schleifen, die einem das Leben erleichtern können:

loop (my $i = 1; $i <= 10; $i++){
	say "Das Quadrat von ", $i, " ist ", $i * $i;
}

loop enthält drei Anweisungen, mit Strichpunkten von einander getrennt. Das erste ist eine Anweisung, die nur vor dem ersten Durchlauf der Schleife durchgeführt wird. Das zweite ist eine Bedingung, wie bei der while-Schleife, die die Schleife abbricht, sobald die Bedingung nicht mehr wahr ist. Das dritte ist eine Anweisung, die nach jedem Durchlauf der Schleife ausgeführt wird.

$i++ erhöht den Wert der Variable $i bei jeder Ausführung um eins, es ist also das gleich wie $i = $i + 1.

Allgemein kann man eine loop-Schleife also wie folgt in eine while-Schleife zerlegen:

loop (s1; s2; s3){
	block;
}

# entspricht

s1;
while s2 {
	block;
	s3;
}