Perl (9) - Cykly

Perl Další hojně užívanou řídící strukturou, bez které se neobejde žádný větší program, je cyklus.

23.5.2005 06:00 | Jiří Václavík | přečteno 44016×

Cyklus umožňuje vykonávat určitou část programu opakovaně. Funguje v podstatě podobně jako podmínka. Na základě vyhodnocení testu se provede blok kódu. Rozdíl mezi podmínkou a cyklem je v tom, že u cyklu se vyhodnocuje test opakovaně. Dokud není test vyhodnocen jako false (nebo v některých speciálních cyklech true), stále se znovu a znovu opakuje blok kódu. Existuje hned několik druhů cyklů.

Cyklus while - předem neznámý počet iterací

Dokud platí test, provádí se blok kódu.

while (test){
    příkazy
}

Cyklus by měl mít možnost být ukončen - tj. vepsat nějaký kód, který ho bude přibližovat ke konci, až nakonec také skončí. Pro naše účely to lze vyřešit například takto:

$i = 0;
while ($i < 10){
    print "Probíhá cyklus $i.\n";
    $i++;
}

10× je proveden kód uvnitř cyklu. Je to stejné, jako kdybychom za sebe nakopírovali 10× blok.

$ perl while1.pl
Probíhá cyklus 0.
Probíhá cyklus 1.
Probíhá cyklus 2.
Probíhá cyklus 3.
Probíhá cyklus 4.
Probíhá cyklus 5.
Probíhá cyklus 6.
Probíhá cyklus 7.
Probíhá cyklus 8.
Probíhá cyklus 9.
$

Ano, je to dost netypická situace pro použití while. V praxi bychom v této situaci bez váhání sáhli po cyklu for. Avšak tento zápis se dá velmi snadno pochopit. Teď už ale přijde na řadu skutečné while. Další příklad totiž do nekonečna vypisuje to, co napíšete. V praxi má taková forma cyklu velmi časté využití.

while ($radek = <STDIN>){
    chomp $radek;
    print "Napsal jste: $radek\n";
}

Po spuštění kódu získáváme výstup:

$ perl while2.pl
Tento cyklus lze ukončit jen násilně. [ENTER]
Tento cyklus lze ukončit jen násilně.
Sám program, pokud není přerušen, by neskončil nikdy. [ENTER]
Sám program, pokud není přerušen, by neskončil nikdy.
[čeká na další vstup]

Zkusme kód poupravit tak, aby se při zadání q nebo exit ukončil. Dosáhneme toho přidáním podmínky (funkce die často nebude stačit. Příští díl osvětlí lepší řešení).

while ($radek = <STDIN>){
    chomp $radek;
    if ($radek eq "q" or $radek eq "exit"){
        die "Konec\n";
    }
    print "Napsal jste: $radek\n";
}

Cyklus for - předem známý počet iterací

Cyklus for se od while liší tím, že je předem znám počet iterací (jde sice změnit v bloku, ale potom je často lepší užít while). for a while jsou vzájemně zaměnitelné (for lze napsat jako while a naopak). Ale většinou je jeden konkrétní vhodnější a ten druhý méně.

Cyklu for se budeme podrobněji věnovat později, neboť je dobré mít zvládnutou práci se seznamy.

Podívejme se nyní pouze na základní užití. V každé iteraci přiřadíme do proměnné číslo postupně od 1 do 10.

for my $i (1 .. 10){
    print "Probíhá cyklus $i.\n";
}

Céčkovské for

Ve zbytku dílu se podívejme na několik zajímavých konstrukcí, které však pravděpodobně nikdy nepoužijeme. Proto klidně zbytek článku bez obav přeskočte.

Taktéž existuje cyklus s klasickou céčkovskou notací. Pro zajímavost ho zde zmíníme, ale kvůli přehlednosti bychom na něj měli hned zase zapomenout a nikdy ho nepoužívat. Syntaxe je následující:

for (počáteční_hodnota_počítadla; konečná_hodnota_počítadla; krok){
    příkazy
}

Počáteční_hodnota_počítadla spouští počítadlo. Konečná_hodnota_počítadla je vlastně testem. Dokud platí, je cyklus opakován (tj. proveden blok kódu). A krokem je výraz, který změní hodnotu počítadla.

for ($i=1; $i<10; $i++){
    print "Probíhá cyklus $i.\n";
}

Výstup je stejný jako u prvního příkladu u while. Nejprve se přiřadí do proměnné $i hodnota 1. Je-li test true, provede se blok, proměnná se inkrementuje (krok) a pokud opět platí test, opakuje se vše znovu. Ve chvíli, kdy test neplatí, je blok přeskočen a program pokračuje za ním.

Složitost výrazů v hlavičce for není omezena jen na takto jednoduché záležitosti. Můžeme třeba přidat další počítadlo. Operátor , (čárka) odděluje se dva výrazy, přičemž první se vyhodnotí a následně zapomene. Druhý výraz se také vyhodnotí a je výsledkem celkového vyhodnocení obou výrazů.

for ($i=0,$j=0; $i<10,$j<10; $i++,$j+=2){
    print "Probíhá cyklus $i. J: $j.\n";
}

Výstup:

$ perl for.pl
Probíhá cyklus 0. J: 0.
Probíhá cyklus 1. J: 2.
Probíhá cyklus 2. J: 4.
Probíhá cyklus 3. J: 6.
Probíhá cyklus 4. J: 8.
$

Je vidět, že počítadlem řídící cyklus je až druhá část výrazu - za čárkou (s proměnnou $j). Výraz s $i je sice každou iteraci zapomenut, ale hodnota je vždy změněna.

počáteční_hodnotu_počítadla, konečnou_hodnotu_počítadla ani krok není povinné uvést. Vynecháme-li počáteční_hodnotu_počítadla, zůstane v $i hodnota, která tam dosud byla. Pokud je nedefinovaná, v první iteraci bude stále proměnná $i nedefinovaná, ale v dalších už nemusí. Je-li totiž v kroku výraz $i++, v druhé iteraci bude hodnota $i = undef + 1 = 1.

Vynecháním konečné_hodnoty_počítadla se cyklus stává nekonečným. Nemá se co vyhodnocovat, a proto je za všech okolností true.

Neuvedení kroku má za následek, že se počítadlo (aspoň v hlavičce cyklu) nebude iterovat.

Tímto způsobem lze vynechat třeba i všechny tři výrazy. Zápis for (;;){...} je ekvivalentní while (1){...}.

Odtud plyne, že počáteční_hodnota_počítadla není nic jiného, než výraz, který se vyhodnotí před započetím cyklu, konečná_hodnota_počítadla je testem a krok výrazem, který se vyhodnocuje na konci každé iterace.

Céčkový cyklus for podle posledního tvrzení můžeme jednoduše přepsat do while (není to úplně přesné, krok by správně měl být až v bloku continue, který představíme příště. Toto řešení neplatí pro případy, kdy je konec bloku vynechán):

počáteční_hodnota_počítadla;
while (konečná_hodnota_počítadla){
    příkazy
    krok;
}

Alternativní cyklus until

Stejně jako jsou ve vztahu if a unless je ve vztahu i while a until. Cyklus je prováděn, dokud je test false. A i zde platí, že bychom takovou konstrukci neměli nadále používat.

Cyklus za příkazem

Cykly while a for se dají v Perlu napsat i za příkaz. Opět to výrazně snižuje čitelnost programu. Ukažme si alespoň pro představu zdrojový kód:

$i = 1;

$i++ while ($i < 10); # k $i se bude tak dlouho přičítat jednička, dokud bude $i nižší než 10
print $i;#10

$i-- until ($i < 0); # nyní se od $i bude pro změnu jednička odečítat a to tak dlouho, dokud nebude $i nižší než 0
print $i;#-1

Cyklus za blokem

Cykly do...while, do...until jsou dalšími variacemi cyklů. Jejich používání znovu nelze v žádném případě doporučit.

Nejprve se provede blok příkazů a až poté se vyhodnotí test (výsledný efekt je stejný, jako kdybyste blok v cyklu zkopírovali před klasický while). Je-li true (while) resp. false (until), vykonává se blok znovu.

$i = 0;

do{ #vykoná se i přesto, že $i má hodnotu 0. Testuje se až za blokem.
    print "Vykonáno\n";
}while ($i == 1);

Za testem musí být stejně jako u konstrukcí do...if a do...unless středník.

I cyklus for lze napsat za příkaz nebo blok. O tom se zmíníme až budu rozebírat další cyklus foreach.

Příklad

Napišme program, který bude ze vstupu přijímat kladná čísla. Zadáním nekladného čísla skončí zadávání (toto číslo se již nepočítá) a program vytiskne počet zadaných čísel, jejich součet a průměr.

Měli bychom získat přibližně takový výstup:

$ perl cisla.pl
Zadávejte kladná čísla, 0 pro konec.
12
994
328
666
666
9004
0
Počet: 6
Součet: 11670
Průměr: 1945
$

Kostra programu bude vypadat velmi podobně jako dnes již zmíněná ukázka kódu k while. Lišit se bude tím, že v každé iteraci se bude aktualizovat součet a počet zadaných čísel a před ukončením se ještě vytisknou výsledky.

#!/usr/bin/perl
use strict;

my $cislo;
my ($pocet, $prumer, $soucet);

print "Zadávejte čísla, 0 pro konec.";
while (chomp($cislo = <STDIN>)){
    if (int $cislo <= 0){
        ...vypsání statistik a konec...
    }

...mezivýpočet...
}

V mezivýpočtu stačí navyšovat součet a počet.

    $soucet += $cislo;
    $pocet++;

Na závěr je třeba dokončit výpis výsledků. Spočítáme průměr, vypíšeme výsledky a ukončíme.

        $prumer = $soucet / $pocet;
        print "Počet: $pocet\n";
        print "Součet: $soucet\n";
        print "Průměr: $prumer\n";
        die "\n"; #Pokud bychom neuvedli konec řádku, vypsala by automatická hláška (Tento řádek by šel lépe řešit pomocí funkce exit, 
#která zatím nebyla probrána)

Když dopíšeme program, je vždy nutné otestovat jeho reakce v závislosti na podmínkách. Všechny případy musí být ošetřeny. To my nemáme, protože program hlásí chybu, pokud neuvedeme žádný parametr. Tedy na začátek bloku v podmínce připišme:

        if ($pocet == 0){
            die "Nezadal jste žádné číslo!\n";
        }

A tím je úloha splněna. Zdrojový kód příkladu.

Online verze článku: http://www.linuxsoft.cz/article.php?id_article=828