Druhy cyklů v PHP

V PHP, stejně jako v ostatních jazycích, existuje více druhů cyklů, které se liší syntaxí a možnostmi využití.

Cykly se dělí podle toho, jaký typ operace chceme nad prvky provádět:

  • for: Obecný cyklus, kde předem známe počet opakování, nebo jej můžeme vypočítat,
  • while, do-while: Obecný cyklus, kde předem neznáme počet opakování,
  • foreach: Procházení polí a objektů,
  • rekurze: Rozložení složitého problému na sadu menších.

For cyklus

For se hodí pro případy, kdy předem známe počet opakování. Perfektně se hodí třeba na lineární procházení intervalů.

Obecně se zapisuje:

for (inicializace; výraz; inkrementace) {
    // tělo cyklu
}
  • inicializace: Definice počátečního stavu cyklu (jaké potřebujeme proměnné),
  • výraz: Podmínka. Dokud platí, tak se bude cykl vykonávat,
  • inkrementace: Změna stavu cyklu oproti předchozímu průchodu (inkrementovat – navýšit o jedničku).

Příkladem může být například vypsání řady čísel od 0 do 10:

1 for ($i = 0; $i <= 10; $i++) {
2     echo $i . '<br>';
3 }

Nebo násobky dvojky od 2 do 100:

1 for ($i = 2; $i <= 100; $i += 2) {
2     echo $i . '<br>';
3 }

While a do-while cyklus

While cyklus běží, dokud platí podmínka. Rozdíl mezi while a do-while je v tom, kdy se bude podmínka vyhodnocovat.

while (výraz) {
    // tělo cyklu
}

Případně:

do {
    // tělo cyklu
} while(výraz);
  • while nejprve vyhodnotí podmínku a poté proběhne vnitřek cyklu,
  • do-while nejprve zpracuje vnitřek cyklu a až poté vyhodnocuje podmínku.

Výhoda je zejména v tom, kdy nemůžeme předem poznat, jestli vůbec cyklus někdy proběhne a chceme zaručit, aby proběhl vždycky třeba aspoň jednou (pak použijeme do-while).

Příklad:

Máme v proměnné $x = 2 uložené číslo. Chceme do proměnné $y vygenerovat taky číslo v intervalu <0; 10> tak, aby bylo různé, než je v proměnné $x. Jednoduše řečeno: Vygenerujte náhodné číslo mezi 010, které není 2.

V takovém případě se hodí použít do-while cyklus. Předem nevíme, kolikrát bude muset cyklus proběhnout (můžeme mít smůlu a budeme se stále trefovat do stejného čísla, v takovém případě poběží cykl dlouho), a zároveň chceme zaručit, že proběhne aspoň jednou, než se vyhodnotí podmínka, protože číslo přeci nejdříve musíme vygenerovat.

1 $x = 2;
2 $y = $x;
3 
4 do {
5    $y = rand(0, 10);
6 } while($x == $y);
7 
8 echo 'X : ' . $x . '<br>';
9 echo 'Y: ' . $y;

Foreach

foreach se perfektně hodí na procházení polí a objektů. Můžeme získat jen konkrétní hodnoty, ale i klíče.

Pokud chceme získat jen konkrétní hodnoty:

foreach ($data as $value) {
    // zpracování dat
}

Případně můžeme získat i klíče:

foreach ($data as $key => $value) {
    // zpracování dat
}

foreach se bezvadně hodí pro procházení reálných dat – třeba výsledků z databáze, nebo polí s klíči:

1 $countries = [
2     'cz' => 'Česká republika',
3     'sk' => 'Slovensko',
4 ];
5 
6 foreach ($countries as $shortcut => $name) {
7     echo $shortcut . ': ' . $name . '<br>';
8 }

Rekurze

Rekurze nastává ve chvíli, kdy funkce nebo metoda volá sama sebe. Slouží k rozložení složitého problému na skupinu menších.

Pochopení rekurze může být pro začátečníky náročné, protože je založena na myšlence předávání zodpovědnosti.

Funkce vlastně říká něco se smyslu: „Já neumím tento problém vyřešit, ale znám někoho, kdo ano…“, tak ho zavolá, on zase někoho, … až dojde k zavolání finálního členu, který problém rozsekne.

Určitě stojí za zmínku, že lze každý rekurzivní algoritmus přepsat na použití cyklů, kde rekurze není potřeba (platí to i obráceně). Rekurze je dobrý sluha, ale špatný pán. Některé typy problémů pomáhá řešit jednoduše a velice efektivně, na jiné věci se zas hodí procházení přes cykly.

Obecně platí, že rekurze má vysokou paměťovou náročnost, protože vytváří stále nové instance a kontexty funkcí a metod. Například pro procházení stromových struktur to je však jediný rozumný způsob, jak problematiku řešit.

Příklad:

Potřebujeme vypočítat faktoriál čísla 5, to se v matematice dělá takto:

5! = 5 * 4 * 3 * 2 * 1

Tedy, dojde k postupnému vynásobení řady čísel od 1 až po číslo, u kterého nás zajímá faktoriál.

Rekurzivně to lze řešit velice elegantně:

1 function factorial(int $n)
2 {
3    if ($n === 0) {
4       return 1;
5    }
6 
7    return $n * factorial($n - 1);
8 }

Případně ještě kratší implementace s použitím ternárního operátoru:

1 function factorial(int $n)
2 {
3     return $n === 0 ? 1 : $n * factorial($n - 1);
4 }

To samé můžeme přepsat na použití cyklů:

 1 function factorial(int $n)
 2 {
 3     $result = 1;
 4 
 5     for ($i = $n; $i > 0; $i--) {
 6         $result *= $i;
 7     }
 8 
 9     return $result;
10  }

Zápis s cyklem je o něco delší, ale zase má výrazně nižší paměťovou náročnost. Pro výpočet používáme jen jednu proměnnou $result, kde průběžně měníme hodnotu a nemusíme stále vytvářet nové instance funkce factorial().

Nekonečné cykly pište hodně opatrně

Velice snadno se může stát, že bude cykl nekonečný (toho dosáhneme tak, že nikdy nebude splněna ukončovací podmínka). S tímto bychom měli počítat a případně si cyklus sami zastavit příkazem break;.

Například hážeme kostkou, dokud nepadne číslo 6. Implementace svádí použít while cyklus:

1 while (true) {
2    $value = rand(1, 6);
3 
4    if ($value === 6) {
5       echo 'Padla hodnota ' . $value;
6       break;
7    }
8 }

Zápisem while(true) jsme si zajistili nekonečný počet opakování, protože true bude pravda vždy. Cykl si musíme tedy sami zastavit konstrukcí break;, co když ale hodnota 6 nikdy nepadne a cykl si nikdy nezastavíme?

Já osobně vždy počítám počet opakování a pokud přesáhne nějakou kritickou mez, tak cykl násilně ukončím. Například pokud přesáhneme 1000 pokusů. V takovém případě je vhodnější místo while použít spíše for a počet průběhů si počítat.

Pokud přesáhneme počet průběhů, je slušnost o tom vývojáře informovat vyhozením výjimky.

 1 for ($i = 0; $i <= 1000; $i++) {
 2    $value = rand(1, 6);
 3 
 4    if ($value === 6) {
 5       echo 'Padla hodnota ' . $value;
 6       break;
 7    }
 8 }
 9 
10 if ($i === 1000) {
11    throw new \Exception('Byl přesáhnut maximální počet hodů.');
12 }

Pokračujte na další článek