23. června 2022

Cykly: while a for

Často potřebujeme opakovat některé akce.

Například vypsat ze seznamu jedno zboží po druhém nebo jen vykonat stejný kód pro každé z čísel od 1 do 10.

Způsob, jak opakovat stejný kód několikrát, poskytují cykly neboli smyčky.

Cykly for…of a for…in

Malé oznámení pro pokročilé čtenáře.

Tento článek probírá jen základní cykly: while, do..while a for(..;..;..).

Jestli jste sem přišli hledat informace o jiných cyklech, najdete je zde:

Jinak čtěte dále.

Cyklus „while“

Cyklus while má následující syntaxi:

while (podmínka) {
  // kód
  // tzv. „tělo cyklu“
}

Dokud je podmínka pravdivá, kód z těla cyklu se bude vykonávat.

Například tento cyklus vypisuje proměnnou i tak dlouho, dokud je i < 3:

let i = 0;
while (i < 3) { // zobrazí 0, pak 1, pak 2
  alert( i );
  i++;
}

Jedno vykonání těla cyklu se nazývá iterace. Cyklus ve výše uvedeném příkladu vykoná tři iterace.

Kdyby v tomto příkladu chyběl příkaz i++, cyklus by se vykonával (teoreticky) donekonečna. V praxi prohlížeče poskytují způsoby, jak takový cyklus zastavit, a v JavaScriptu na serverové straně můžeme proces zastavit („shodit“) sami.

Platnou podmínkou cyklu může být jakákoli proměnná nebo výraz, nejenom porovnání: příkaz while podmínku vyhodnotí a převede na typ boolean.

Například while (i != 0) se dá napsat kratším způsobem jako while (i):

let i = 3;
while (i) { // když i bude 0, podmínka bude nepravdivá a cyklus skončí
  alert( i );
  i--;
}
Tělo tvořené jedním příkazem nemusí mít složené závorky

Obsahuje-li tělo cyklu jen jediný příkaz, můžeme složené závorky {…} vynechat:

let i = 3;
while (i) alert(i--);

Cyklus „do…while“

Ověření podmínky můžeme přesunout až za tělo cyklu, použijeme-li syntaxi do..while:

do {
  // tělo cyklu
} while (podmínka);

Cyklus nejprve vykoná tělo, pak ověří podmínku, a dokud je pravdivá, bude vykonávat tělo znovu a znovu.

Příklad:

let i = 0;
do {
  alert( i );
  i++;
} while (i < 3);

Tuto formu syntaxe byste měli používat jen tehdy, když chcete, aby se tělo cyklu vykonalo vždy aspoň jednou, bez ohledu na pravdivost podmínky. Obvykle se dává přednost předchozí formě: while(…) {…}.

Cyklus „for“

Cyklus for je složitější, ale také nejčastěji používaný.

Vypadá takto:

for (začátek; podmínka; krok) {
  // ... tělo cyklu ...
}

Význam jednotlivých částí si objasníme na příkladu. Níže uvedený cyklus vykoná alert(i) pro i od 0 až do (ale ne včetně) 3:

for (let i = 0; i < 3; i++) { // zobrazí 0, pak 1, pak 2
  alert(i);
}

Prozkoumáme příkaz for po částech:

část
začátek let i = 0 Vykoná se jednou po vstupu do cyklu.
podmínka i < 3 Kontroluje se před každou iterací cyklu. Je-li nepravdivá, cyklus skončí.
tělo alert(i) Vykonává se stále znovu, dokud je podmínka pravdivá.
krok i++ Vykoná se po těle cyklu při každé iteraci.

Všeobecný algoritmus cyklu funguje takto:

Vykonej začátek
→ (platí-li podmínka → vykonej tělo a vykonej krok)
→ (platí-li podmínka → vykonej tělo a vykonej krok)
→ (platí-li podmínka → vykonej tělo a vykonej krok)
→ ...

To znamená, že začátek se vykoná jednou a pak se iteruje: po každém testu podmínky se vykoná tělo a krok.

Jestliže s cykly teprve začínáte, pomůže vám vrátit se k příkladu a na papíře si krok po kroku projít, jak se vykoná.

V našem případě se stane přesně toto:

// for (let i = 0; i < 3; i++) alert(i)

// vykonej začátek
let i = 0
// platí-li podmínka → vykonej tělo a vykonej krok
if (i < 3) { alert(i); i++ }
// platí-li podmínka → vykonej tělo a vykonej krok
if (i < 3) { alert(i); i++ }
// platí-li podmínka → vykonej tělo a vykonej krok
if (i < 3) { alert(i); i++ }
// ...konec, protože nyní je i == 3
Inline deklarace proměnné

„Čítačová“ proměnná i je zde deklarována rovnou v cyklu. To se nazývá „inline“ (na místě) deklarace proměnné. Takové proměnné jsou viditelné jen uvnitř cyklu.

for (let i = 0; i < 3; i++) {
  alert(i); // 0, 1, 2
}
alert(i); // chyba, tato proměnná neexistuje

Namísto definice nové proměnné můžeme použít existující:

let i = 0;

for (i = 0; i < 3; i++) { // použijeme existující proměnnou
  alert(i); // 0, 1, 2
}

alert(i); // 3, je viditelná, protože byla deklarována mimo cyklus

Vynechávání částí

Kteroukoli část for můžeme vynechat.

Například můžeme vynechat začátek, jestliže nechceme na začátku cyklu nic provádět.

Například zde:

let i = 0; // už máme proměnnou i deklarovanou a přiřazenou

for (; i < 3; i++) { // nepotřebujeme „začátek“
  alert( i ); // 0, 1, 2
}

Můžeme vynechat i část krok:

let i = 0;

for (; i < 3;) {
  alert( i++ );
}

Pak bude cyklus stejný jako while (i < 3).

Ve skutečnosti můžeme vynechat všechno a vytvořit tím nekonečnou smyčku:

for (;;) {
  // opakuje se neustále
}

Všimněte si, že dva středníky ; ve for musejí být uvedeny, jinak nastane syntaktická chyba.

Opuštění smyčky

Za běžných okolností se cyklus ukončí, když jeho podmínka přestane být splněna.

Kdykoli si však můžeme ukončení vynutit použitím speciální direktivy break.

Například níže uvedený cyklus se uživatele ptá na sérii čísel, a když uživatel žádné číslo nezadá, cyklus skončí:

let součet = 0;

while (true) {

  let hodnota = +prompt("Zadejte číslo", '');

  if (!hodnota) break; // (*)

  součet += hodnota;

}
alert( 'Součet: ' + součet );

Direktiva break na řádku (*) se aktivuje, jestliže uživatel zadá prázdný řádek nebo zruší vstup. Okamžitě ukončí cyklus a předá řízení na první řádek za cyklem, konkrétně alert.

Kombinace „nekonečná smyčka + break, když je zapotřebí“ je výhodná v situacích, kdy potřebujeme podmínku cyklu ověřovat ne na začátku nebo na konci cyklu, ale uprostřed cyklu nebo dokonce na několika místech jeho těla.

Pokračování k další iteraci

Direktiva continue je „slabší verzí“ break. Nezastaví celý cyklus, ale zastaví jen právě probíhající iteraci a přinutí cyklus začít novou (jestliže podmínka platí).

Můžeme ji použít, když jsme hotovi s právě probíhající iterací a rádi bychom okamžitě přešli k další.

Níže uvedený cyklus využívá continue k vypsání jen lichých hodnot:

for (let i = 0; i < 10; i++) {

  // je-li podmínka pravdivá, přeskočíme zbytek těla
  if (i % 2 == 0) continue;

  alert(i); // 1, pak 3, 5, 7, 9
}

Pro sudé hodnoty i direktiva continue ukončí vykonávání těla a předá řízení další iteraci for (s dalším číslem). Proto se alert bude volat jedině pro liché hodnoty.

Direktiva continue pomáhá redukovat vnoření

Cyklus, který zobrazuje liché hodnoty, by mohl vypadat i takto:

for (let i = 0; i < 10; i++) {

  if (i % 2) {
    alert( i );
  }

}

Z technického pohledu je to stejné jako výše uvedený příklad. Bezpochyby můžeme namísto použití continue vnořit kód do bloku if.

Jako vedlejší efekt jsme však vytvořili jednu další úroveň vnoření (volání alert uvnitř složených závorek). Je-li kód uvnitř if delší než pár řádků, může to snížit jeho celkovou čitelnost.

break/continue nesmějí být na pravé straně „?“

Všimněte si, že syntaktické konstrukce, které nejsou výrazy, nelze použít s ternárním operátorem „?“. Konkrétně tam nejsou povoleny direktivy jako break/continue.

Vezměme si například tento kód:

if (i > 5) {
  alert(i);
} else {
  continue;
}

…a přepišme jej pomocí otazníku:

(i > 5) ? alert(i) : continue; // continue tady nesmí být

…přestane to fungovat: nastane syntaktická chyba.

To je další důvod, proč nepoužívat operátor otazníku ? namísto if.

Návěští pro break/continue

Někdy se potřebujeme dostat ven z několika vnořených cyklů najednou.

Například v níže uvedeném kódu vykonáváme cyklus nad i a j, který se ptá na hodnoty (i, j) od (0,0) do (2,2):

for (let i = 0; i < 3; i++) {

  for (let j = 0; j < 3; j++) {

    let vstup = prompt(`Hodnota na souřadnicích (${i},${j})`, '');

    // co když chceme vyskočit odtud až na „Hotovo“ níže?
  }
}

alert('Hotovo!');

Potřebujeme způsob, jak tento proces zastavit, jestliže uživatel zruší vstup.

Pouhé break po vstup by ukončilo jen vnitřní cyklus. To nám však nestačí. V takovém případě použijeme návěští.

Návěští je identifikátor s dvojtečkou před cyklem:

názevNávěští: for (...) {
  ...
}

Příkaz break <názevNávěští> v níže uvedeném cyklu vyskočí z uvedeného návěští:

vnější: for (let i = 0; i < 3; i++) {

  for (let j = 0; j < 3; j++) {

    let vstup = prompt(`Hodnota na souřadnicích (${i},${j})`, '');

    // je-li zadán prázdný řetězec nebo zrušen vstup, vyskočí se z obou cyklů
    if (!vstup) break vnější; // (*)

    // provedeme něco s hodnotou...
  }
}

alert('Hotovo!');

Ve výše uvedeném kódu break vnější najde návěští s názvem vnější a vyskočí z jeho cyklu.

Řízení se tedy předá přímo z (*) na alert('Hotovo!').

Můžeme návěští umístit i na samostatný řádek:

vnější:
for (let i = 0; i < 3; i++) { ... }

Návěští můžeme použít i v direktivě continue. V takovém případě se zbytek kódu přeskočí a další iterace bude v cyklu s uvedeným návěštím.

Návěští nám neumožňují „skákat“ kamkoli!

Návěští nám neumožňují skákat na libovolné místo v kódu.

Například nejde udělat toto:

break návěští; // skok na návěští níže (nebude fungovat)

návěští: for (...)

Direktiva break musí být uvnitř kódového bloku. Technicky to může být jakýkoli blok kódu s návěštím, např.:

návěští: {
  // ...
  break návěští; // toto funguje
  // ...
}

…Avšak v 99,9% případů se break používá uvnitř cyklů, jak jsme viděli ve výše uvedených příkladech.

Direktiva continue může být jedině uvnitř cyklu.

Shrnutí

Uvedli jsme tři druhy cyklů:

  • while – Podmínka se ověří před každou iterací.
  • do..while – Podmínka se ověří po každé iteraci.
  • for (;;) – Podmínka se ověří před každou iterací, jsou možná i další nastavení.

K vytvoření „nekonečné“ smyčky se obvykle používá konstrukce while(true). Takovou smyčku lze zastavit direktivou break, stejně jako každou jinou.

Pokud nechceme už nic provádět v současné iteraci a chceme rovnou přejít k další, použijeme direktivu continue.

break/continue podporují návěští před cyklem. Návěští je jediný způsob, jak může break/continue ve vnořeném cyklu vyskočit z vnějšího cyklu.

Úlohy

důležitost: 3

Jaká je poslední hodnota, kterou vypíše tento kód? Proč?

let i = 3;

while (i) {
  alert( i-- );
}

Odpověď: 1.

let i = 3;

while (i) {
  alert( i-- );
}

Každá iterace cyklu sníží i o 1. Ověření while(i) zastaví cyklus, když i = 0.

Jednotlivé kroky cyklu tedy vytvoří následující posloupnost:

let i = 3;

alert(i--); // zobrazí 3, sníží i na 2

alert(i--) // zobrazí 2, sníží i na 1

alert(i--) // zobrazí 1, sníží i na 0

// hotovo, ověření while(i) ukončí cyklus
důležitost: 4

Pro oba cykly si zapište hodnoty, které budou vypsány, a pak je porovnejte s řešením.

Vypíše alert v obou cyklech stejné hodnoty, nebo ne?

  1. Prefixová notace ++i:

    let i = 0;
    while (++i < 5) alert( i );
  2. Postfixová notace i++

    let i = 0;
    while (i++ < 5) alert( i );

Tato úloha ukazuje, jak může prefixová a postfixová notace vést k rozdílným výsledkům, když je použijeme v porovnání.

  1. Od 1 do 4

    let i = 0;
    while (++i < 5) alert( i );

    První hodnota je i = 1, jelikož ++i nejprve zvýší i a pak vrátí novou hodnotu. První porovnání je tedy 1 < 5 a alert zobrazí 1.

    Následují 2, 3, 4… – hodnoty se zobrazí jedna po druhé. Porovnání se vždy dívá na zvýšenou hodnotu, protože ++ je před proměnnou.

    Nakonec i = 4 se zvýší na 5, porovnání while(5 < 5) neuspěje a cyklus skončí. Takže 5 se nezobrazí.

  2. Od 1 do 5

    let i = 0;
    while (i++ < 5) alert( i );

    První hodnota je opět i = 1. Postfixová notace i++ zvýší i a pak vrátí starou hodnotu, takže porovnání i++ < 5 se dívá na i = 0 (na rozdíl od ++i < 5).

    Avšak alert se volá odděleně. Je to další příkaz, který se spustí až po zvýšení a porovnání. Proto obdrží aktuální i = 1.

    Následují 2, 3, 4…

    Zastavme se u i = 4. Prefixová notace ++i by je zvýšila a v porovnání by použila 5. Tady však máme postfixovou notaci i++. Ta zvýší i na 5, ale vrátí starou hodnotu. Proto se provede porovnání while(4 < 5) – pravda, tudíž řízení přejde k alert.

    Hodnota i = 5 je poslední, jelikož další krok while(5 < 5) dává nepravdu.

důležitost: 4

Pro oba cykly zapište hodnoty, které budou zobrazeny, a pak je porovnejte s řešením.

Vypíše alert v obou cyklech stejné hodnoty, nebo ne?

  1. Postfixová notace:

    for (let i = 0; i < 5; i++) alert( i );
  2. Prefixová notace:

    for (let i = 0; i < 5; ++i) alert( i );

Odpověď: od 0 do 4 v obou případech.

for (let i = 0; i < 5; ++i) alert( i );

for (let i = 0; i < 5; i++) alert( i );

Lze to snadno odvodit z algoritmu pro for:

  1. Nejdříve se jedenkrát vykoná i = 0 (začátek).
  2. Ověří se podmínka i < 5.
  3. Je-li true, vykoná se tělo cyklu alert(i) a pak i++.

Zvýšení i++ je odděleno od testu podmínky (2). Je to jen další příkaz.

Hodnota vrácená zvýšením se tady nepoužívá, takže mezi i++ a ++i není žádný rozdíl.

důležitost: 5

Použitím cyklu for vypište sudá čísla od 2 do 10.

Spustit demo

for (let i = 2; i <= 10; i++) {
  if (i % 2 == 0) {
    alert( i );
  }
}

K získání zbytku po dělení dvěma a ověření sudosti zde používáme operátor „modulo“ %.

důležitost: 5

Přepište kód tak, že cyklus for zaměníte za cyklus while, aniž by se změnilo jeho chování (výstup má zůstat stejný).

for (let i = 0; i < 3; i++) {
  alert( `číslo ${i}!` );
}
let i = 0;
while (i < 3) {
  alert( `číslo ${i}!` );
  i++;
}
důležitost: 5

Napište cyklus, který se bude ptát na číslo větší než 100. Pokud návštěvník zadá jiné číslo, zeptejte se ho znovu.

Cyklus se musí ptát na číslo tak dlouho, než návštěvník zadá číslo větší než 100 nebo zruší vstup či zadá prázdný řádek.

Zde předpokládáme, že uživatel zadává pouze čísla. V této úloze nemusíte implementovat zvláštní zacházení s nečíselnými vstupy.

Spustit demo

let číslo;

do {
  číslo = prompt("Zadejte číslo větší než 100", 0);
} while (číslo <= 100 && číslo);

Cyklus do..while se opakuje, dokud jsou obě podmínky splněny:

  1. Podmínka číslo <= 100 – tedy že zadaná hodnota stále není větší než 100.
  2. Podmínka && číslo je nepravdivá, když číslo je null nebo prázdný řetězec. Pak se cyklus while rovněž zastaví.

P.S. Jestliže číslo je null, pak číslo <= 100 vydá true, takže bez druhé podmínky by se cyklus nezastavil, kdyby uživatel stiskl Storno. Obě podmínky jsou zapotřebí.

důležitost: 3

Celé číslo větší než 1 se nazývá prvočíslo, jestliže není dělitelné beze zbytku jiným celým číslem než 1 a sebou samým.

Jinými slovy, číslo n > 1 je prvočíslo, jestliže není dělitelné beze zbytku jiným číslem než 1 a n.

Například 5 je prvočíslo, protože není dělitelné beze zbytku číslem 2, 3 ani 4.

Napište kód, který vypíše všechna prvočísla v intervalu od 2 do n.

Například pro n = 10 bude výsledek 2,3,5,7.

P.S. Kód by měl fungovat pro jakékoli n. Neměl by být vyladěn jen pro nějakou pevnou hodnotu.

Pro tuto úlohu existuje mnoho algoritmů.

Použijeme vnořený cyklus:

pro každé i v intervalu {
  ověř, zda i má dělitele mezi 1..i
  pokud ano => i není prvočíslo
  pokud ne => i je prvočíslo, zobraz ho
}

Kód s použitím návěští:

let n = 10;

dalšíPrvočíslo:
for (let i = 2; i <= n; i++) { // pro každé i...

  for (let j = 2; j < i; j++) { // hledáme dělitele...
    if (i % j == 0) continue dalšíPrvočíslo; // není to prvočíslo, přejdeme k dalšímu i
  }

  alert( i ); // je to prvočíslo
}

Je zde mnoho prostoru k optimalizaci. Můžeme se například dívat jen na dělitele od 2 do odmocniny i. Kdybychom však chtěli být opravdu efektivní i pro velké intervaly, museli bychom změnit přístup a zaměřit se na vysokou matematiku a složité algoritmy, např. kvadratické síto, Obecné číselné teoretické síto (GNFS) atd.

Mapa tutoriálu

Komentáře

přečtěte si před komentováním…
  • Máte-li návrhy na zlepšení, vytvořte prosím issue na GitHubu nebo pull request místo komentáře.
  • Pokud v článku něčemu nerozumíte, napište prosím, čemu přesně a na kterém místě.
  • Pro vložení několika slov kódu použijte značku <code>, pro několik řádků je obalte značkou <pre>, pro více než 10 řádků vložte odkaz na pískoviště (plnkr, jsbin, codepen…)