Pętle

Komputer nie nudzi się powtarzaniem. Cztery rodzaje pętli wystarczą, żeby przejść przez tablicę, policzyć sumę tysiąca liczb, narysować choinkę ze znaków i pytać użytkownika tak długo, aż poda poprawne dane.

TypeScript for · while for…of break · continue 55 min
CEL LEKCJI

Czego się dziś nauczysz

  • Napiszesz pętlę for z licznikiem rosnącym, malejącym i o dowolnym kroku
  • Wybierzesz właściwą pętlę do zadania: for, while, for…of
  • Przerwiesz pętlę przez break i pominiesz obieg przez continue
  • Zbudujesz pętlę zagnieżdżoną i wypiszesz nią tabliczkę mnożenia
  • Rozpoznasz pętlę nieskończoną i wskażesz jej przyczynę
TEORIA

Pętla for

budowa pętli for
for ( let i = 0 ;  i < 5 ;  i++ ) {
        │            │        │
        │            │        └─ 3. co zrobić po każdym obiegu
        │            └────────── 2. warunek: dopóki prawda, powtarzaj
        └─────────────────────── 1. przygotowanie, wykonane raz na starcie
}

kolejność:  1 → 2 → blok → 3 → 2 → blok → 3 → 2 → … → 2 (fałsz) → koniec
src/index.ts
// od 0 do 4
for (let i = 0; i < 5; i++) {
  console.log(`Obieg numer ${i}`);
}

// od 1 do 10 — naturalniejsze przy liczeniu
for (let i = 1; i <= 10; i++) {
  console.log(i);
}

// w dół
for (let i = 10; i >= 1; i--) {
  console.log(i);
}

// co trzy
for (let i = 0; i <= 30; i += 3) {
  console.log(i);
}

Zmienna sterująca (tradycyjnie i od indeks) istnieje tylko wewnątrz pętli. Po jej zakończeniu znika — to ten sam zasięg blokowy, który poznałeś przy if.

Klasyczna pomyłka o jeden

Przy tablicy pięcioelementowej warunek i <= t.length daje sześć obiegów, a ostatni sięga poza tablicę. Zapamiętaj parę: od zera do < length.

TEORIA

while i do…while

while powtarza blok, dopóki warunek jest prawdziwy. Używa się jej, gdy nie wiadomo z góry, ile będzie obiegów:

src/index.ts
// ile razy trzeba podzielić liczbę przez 2, żeby zejść do jedynki?
let liczba = 1000;
let kroki = 0;

while (liczba > 1) {
  liczba = Math.floor(liczba / 2);
  kroki++;
}

console.log(`Potrzeba ${kroki} kroków`);   // 9

do…while różni się jednym: warunek sprawdzany jest po wykonaniu bloku, więc blok wykona się zawsze co najmniej raz:

src/index.ts
let proba = 0;
let wylosowana: number;

do {
  wylosowana = Math.floor(Math.random() * 6) + 1;
  proba++;
  console.log(`Rzut ${proba}: ${wylosowana}`);
} while (wylosowana !== 6);

console.log(`Szóstka wypadła po ${proba} rzutach`);

Losowanie musi się odbyć, zanim można sprawdzić wynik — to typowa sytuacja dla do…while. Ten sam schemat obsługuje pytanie użytkownika o dane: najpierw pytamy, potem sprawdzamy, czy odpowiedź jest poprawna.

Pętla nieskończona

Pętla, której warunek nie stanie się fałszywy i która nie wykona break ani innego wyjścia, może działać bez końca. Przerwij taki program przez Ctrl+C. Sprawdź, co zapewnia postęp lub zakończenie; nie każda poprawna pętla musi zmieniać wartość bezpośrednio w warunku.

PRAKTYKA

for…of i forEach

Gdy przechodzisz przez tablicę i nie potrzebujesz numeru pozycji, for…of jest najczytelniejsza — nie ma w niej licznika, którym można się pomylić:

src/index.ts
const przedmioty = ["INF.03", "INF.04", "matematyka"];

for (const p of przedmioty) {
  console.log(p);
}

// gdy potrzebny jest też numer — entries() daje krotkę [indeks, wartość]
for (const [i, p] of przedmioty.entries()) {
  console.log(`${i + 1}. ${p}`);
}

// forEach — ta sama praca, zapis funkcyjny
przedmioty.forEach((p, i) => {
  console.log(`${i + 1}. ${p}`);
});

// for…of działa też na tekście — znak po znaku
for (const znak of "Lębork") {
  console.log(znak);
}

W forEach nie działa break

forEach wywołuje funkcję dla każdego elementu i nie da się jej przerwać w połowie (return pomija tylko jeden element, jak continue). Gdy potrzebujesz wyjść wcześniej — użyj for…of.

DECYZJA

Którą pętlę wybrać

PętlaKiedyPrzykład zadania
forznasz liczbę obiegów lub potrzebujesz indeksu wypisz liczby 1–100, przejdź tablicę wstecz, porównaj dwie tablice po indeksie
for…ofprzechodzisz elementy tablicy lub znaki tekstu policz sumę ocen, znajdź najdłuższe słowo
whilenie wiesz, ile obiegów będzie potrzeba dziel, aż zejdziesz do jedynki; czytaj dane, aż się skończą
do…whileblok musi wykonać się co najmniej raz pytaj o dane, aż będą poprawne; losuj, aż wypadnie szóstka

Praktyczna kolejność wyboru

Najpierw sprawdź, czy wystarczy for…of — jest najkrótsza i najbezpieczniejsza. Jeśli potrzebujesz indeksu, sięgnij po for. while zostaw do sytuacji naprawdę nieprzewidywalnych.

TEORIA

break i continue

src/index.ts
const oceny = [5, 4, 1, 3, 5];

// break — przerywa całą pętlę
for (const ocena of oceny) {
  if (ocena === 1) {
    console.log("Znaleziono jedynkę — przerywam sprawdzanie");
    break;
  }
  console.log(`Ocena ${ocena} w porządku`);
}

console.log("───");

// continue — pomija resztę bieżącego obiegu i idzie dalej
let suma = 0;
for (const ocena of oceny) {
  if (ocena === 1) {
    continue;              // jedynek nie wliczamy
  }
  suma += ocena;
}
console.log(`Suma bez jedynek: ${suma}`);   // 17
wynik w konsoli
Ocena 5 w porządku
Ocena 4 w porządku
Znaleziono jedynkę — przerywam sprawdzanie
───
Suma bez jedynek: 17

Typowe zastosowanie break to wyszukiwanie: gdy znajdziesz to, czego szukasz, nie ma powodu przeglądać reszty:

src/index.ts
const uczniowie = ["Ala", "Bartek", "Cezary", "Dorota"];
const szukany = "Cezary";

let znalezionoNa = -1;

for (let i = 0; i < uczniowie.length; i++) {
  if (uczniowie[i] === szukany) {
    znalezionoNa = i;
    break;
  }
}

if (znalezionoNa === -1) {
  console.log(`${szukany} nie występuje na liście`);
} else {
  console.log(`${szukany} jest na pozycji ${znalezionoNa + 1}`);
}
PRAKTYKA

Pętle zagnieżdżone

Pętla w pętli: dla każdego obiegu zewnętrznego wewnętrzna wykonuje się w całości. Przy dwóch pętlach po 10 obiegów daje to 100 wykonań bloku.

src/index.ts — tabliczka mnożenia
// nagłówek
let naglowek = "    ";
for (let k = 1; k <= 10; k++) {
  naglowek += String(k).padStart(4);
}
console.log(naglowek);
console.log("─".repeat(44));

// wiersze
for (let w = 1; w <= 10; w++) {
  let wiersz = String(w).padStart(2) + " │";
  for (let k = 1; k <= 10; k++) {
    wiersz += String(w * k).padStart(4);
  }
  console.log(wiersz);
}
wynik w konsoli (fragment)
       1   2   3   4   5   6   7   8   9  10
────────────────────────────────────────────
 1 │   1   2   3   4   5   6   7   8   9  10
 2 │   2   4   6   8  10  12  14  16  18  20
 3 │   3   6   9  12  15  18  21  24  27  30
 4 │   4   8  12  16  20  24  28  32  36  40
 5 │   5  10  15  20  25  30  35  40  45  50
 6 │   6  12  18  24  30  36  42  48  54  60
 7 │   7  14  21  28  35  42  49  56  63  70
 8 │   8  16  24  32  40  48  56  64  72  80
 9 │   9  18  27  36  45  54  63  72  81  90
10 │  10  20  30  40  50  60  70  80  90 100

Rysowanie kształtów

src/index.ts — choinka
const wysokosc = 6;

for (let i = 1; i <= wysokosc; i++) {
  const spacje = " ".repeat(wysokosc - i);
  const gwiazdki = "*".repeat(2 * i - 1);
  console.log(spacje + gwiazdki);
}
console.log(" ".repeat(wysokosc - 1) + "|");
wynik w konsoli
     *
    ***
   *****
  *******
 *********
***********
     |

Dwa sposoby rysowania

Powyżej użyliśmy repeat, bo to krótsze. Na egzaminie i w zadaniach z innych języków częściej rysuje się pętlą wewnętrzną: for (let j = 0; j < 2 * i - 1; j++) wiersz += "*"; Warto umieć oba — wynik jest identyczny.

break w pętli zagnieżdżonej

break przerywa tylko tę pętlę, w której stoi. Żeby wyjść z obu naraz, używa się etykiety:

src/index.ts
const plansza = [
  [0, 0, 0],
  [0, 9, 0],
  [0, 0, 0]
];

szukanie:
for (let w = 0; w < plansza.length; w++) {
  for (let k = 0; k < plansza[w].length; k++) {
    if (plansza[w][k] === 9) {
      console.log(`Znaleziono w wierszu ${w}, kolumnie ${k}`);
      break szukanie;      // wychodzi z OBU pętli
    }
  }
}

Etykiety używaj oszczędnie — zwykle lepszym rozwiązaniem jest przeniesienie szukania do osobnej funkcji i wyjście przez return (lekcja 11).

PRZYKŁAD Z OMÓWIENIEM

Analiza frekwencji klasy

Program przetwarza dane obecności zapisane jako tekst, liczy statystyki dla każdego ucznia i dla całej klasy, wyszukuje uczniów zagrożonych i rysuje wykres frekwencji.

src/index.ts
// o = obecny, n = nieobecny, s = spóźnienie
const dane = [
  "Kowalska Marta:ooonooooosoo",
  "Nowak Piotr:onnnooonnooo",
  "Wiśniewski Jan:oooooooooooo",
  "Dąbrowska Ewa:oonoosooonno"
];

const PROG_ZAGROZENIA = 75;

let sumaFrekwencji = 0;
let najlepszy = "";
let najlepszaFrekwencja = -1;
const zagrozeni: string[] = [];

console.log("UCZEŃ".padEnd(20) + "OB.  NB.  SP.   FREK.   WYKRES");
console.log("═".repeat(62));

for (const wiersz of dane) {
  // ── rozbicie wiersza ──
  const [nazwisko, obecnosci] = wiersz.split(":");

  // ── zliczanie znak po znaku ──
  let obecny = 0;
  let nieobecny = 0;
  let spoznienie = 0;

  for (const znak of obecnosci) {
    if (znak === "o") {
      obecny++;
    } else if (znak === "n") {
      nieobecny++;
    } else if (znak === "s") {
      spoznienie++;
    } else {
      continue;                    // nieznany znak pomijamy
    }
  }

  // ── obliczenia ──
  const wszystkie = obecnosci.length;
  const frekwencja = ((obecny + spoznienie) / wszystkie) * 100;
  sumaFrekwencji += frekwencja;

  // ── wiersz raportu ──
  const pasek = "█".repeat(Math.round(frekwencja / 10));
  console.log(
    nazwisko.padEnd(20) +
    String(obecny).padStart(3) +
    String(nieobecny).padStart(5) +
    String(spoznienie).padStart(5) +
    (frekwencja.toFixed(1) + "%").padStart(8) + "   " +
    pasek
  );

  // ── wyszukiwanie skrajnych przypadków ──
  if (frekwencja > najlepszaFrekwencja) {
    najlepszaFrekwencja = frekwencja;
    najlepszy = nazwisko;
  }
  if (frekwencja < PROG_ZAGROZENIA) {
    zagrozeni.push(nazwisko);
  }
}

// ── podsumowanie klasy ──
console.log("═".repeat(62));
console.log(`Średnia frekwencja klasy: ${(sumaFrekwencji / dane.length).toFixed(1)}%`);
console.log(`Najwyższa frekwencja:     ${najlepszy} (${najlepszaFrekwencja.toFixed(1)}%)`);

if (zagrozeni.length === 0) {
  console.log("Brak uczniów poniżej progu.");
} else {
  console.log(`Poniżej ${PROG_ZAGROZENIA}%: ${zagrozeni.join(", ")}`);
}
wynik w konsoli
UCZEŃ               OB.  NB.  SP.   FREK.   WYKRES
══════════════════════════════════════════════════════════════
Kowalska Marta       10    1    1   91.7%   █████████
Nowak Piotr           7    5    0   58.3%   ██████
Wiśniewski Jan       12    0    0  100.0%   ██████████
Dąbrowska Ewa         8    3    1   75.0%   ████████
══════════════════════════════════════════════════════════════
Średnia frekwencja klasy: 81.3%
Najwyższa frekwencja:     Wiśniewski Jan (100.0%)
Poniżej 75%: Nowak Piotr

Cztery wzorce w jednym programie

WzorzecGdzie i jak
Pętla w pętli Zewnętrzna idzie po uczniach, wewnętrzna po znakach obecności. Klasyczne przetwarzanie danych dwuwymiarowych.
Akumulator sumaFrekwencji i liczniki obecny, nieobecny — zmienna deklarowana przed pętlą, powiększana w środku, odczytywana po.
Szukanie maksimum najlepszaFrekwencja startuje od -1, żeby pierwszy uczeń na pewno ją przebił. Razem z nazwiskiem zapisujemy dwie wartości naraz.
Zbieranie do tablicy zagrozeni.push(nazwisko) — budowanie listy wyników wewnątrz pętli, a potem sprawdzenie length, żeby wiedzieć, czy coś w niej jest.

Sprawdź to sam

Dopisz do tablicy piątego ucznia z frekwencją poniżej progu i zobacz, jak zmienia się podsumowanie. Potem zamień wewnętrzną drabinkę if / else if na instrukcję switch — to dobre ćwiczenie z poprzedniej lekcji.

CZĘSTE BŁĘDY

Na co uważać

KodProblem
for (let i = 0; i <= t.length; i++) Jeden obieg za dużo — ostatni element to undefined.
while (i < 10) { console.log(i); } Brak i++ — pętla nieskończona.
for (let i = 0; i < 5; i++); Średnik po nawiasie: pętla kręci się pusto, a blok wykona się raz.
Modyfikowanie tablicy w trakcie for…of push lub splice podczas przechodzenia potrafi pominąć elementy albo zapętlić program. Zbieraj wyniki do osobnej tablicy.
let suma = 0; wewnątrz pętli Zerowanie przy każdym obiegu — akumulator deklaruj przed pętlą.
break w forEach Błąd składni. forEach nie da się przerwać; użyj for…of.
for (const x in tablica) Dostajesz indeksy jako tekst, nie wartości. Przy tablicach używaj of.
ZADANIA

Zadania

ZAD 1Przewidź wynik★☆☆
zagadka.ts
let s = "";
for (let i = 1; i <= 5; i++) {
  if (i === 3) continue;
  if (i === 5) break;
  s += i;
}
console.log(s);

let n = 0;
let licznik = 0;
while (n < 20) {
  n += 7;
  licznik++;
}
console.log(`${n} ${licznik}`);
pokaż rozwiązanie
124
21 3

Pierwsza pętla: 1 i 2 dopisane, 3 pominięte przez continue, 4 dopisane, przy 5 break kończy pętlę. Druga: 7, 14, 21 — po trzecim dodaniu warunek n < 20 jest fałszywy, ale wartość została już powiększona.

ZAD 2FizzBuzz★☆☆

Wypisz liczby 1–50, zastępując wielokrotności 3 przez Fizz, 5 przez Buzz, a 15 przez FizzBuzz. To popularne ćwiczenie kolejności warunków.

pokaż rozwiązanie
for (let i = 1; i <= 50; i++) {
  if (i % 15 === 0) {
    console.log("FizzBuzz");
  } else if (i % 3 === 0) {
    console.log("Fizz");
  } else if (i % 5 === 0) {
    console.log("Buzz");
  } else {
    console.log(i);
  }
}

Kluczowa jest kolejność: warunek podzielności przez 15 musi być pierwszy, bo liczba podzielna przez 15 jest też podzielna przez 3 i zatrzymałaby się na tamtej gałęzi. To dokładnie problem kolejności warunków z lekcji 06.

ZAD 3Statystyki tablicy bez gotowców★★☆

Mając tablicę 15 losowych liczb (Math.floor(Math.random() * 100) w pętli), policz bez używania Math.min, Math.max i metod tablicowych: sumę, średnią, wartość najmniejszą i największą, liczbę elementów parzystych oraz ile elementów jest powyżej średniej. Ostatnie wymaga dwóch przejść przez tablicę — zastanów się dlaczego.

pokaż wskazówki

Średnią znasz dopiero po zsumowaniu wszystkich elementów, więc porównanie z nią musi odbyć się w drugiej pętli. To bardzo częsty schemat: pierwsze przejście liczy statystykę, drugie z niej korzysta.

ZAD 4Liczby pierwsze★★☆

Wypisz wszystkie liczby pierwsze z przedziału od 2 do 100. Liczba jest pierwsza, gdy dzieli się bez reszty tylko przez 1 i samą siebie. Użyj pętli zagnieżdżonej i przerwij wewnętrzną przez break, gdy tylko znajdziesz dzielnik. Policz też, ile ich jest.

pokaż rozwiązanie
let ile = 0;
let wiersz = "";

for (let n = 2; n <= 100; n++) {
  let pierwsza = true;

  for (let d = 2; d * d <= n; d++) {
    if (n % d === 0) {
      pierwsza = false;
      break;
    }
  }

  if (pierwsza) {
    wiersz += String(n).padStart(4);
    ile++;
    if (ile % 10 === 0) {
      console.log(wiersz);
      wiersz = "";
    }
  }
}

if (wiersz !== "") console.log(wiersz);
console.log(`Znaleziono ${ile} liczb pierwszych`);

Warunek d * d <= n zamiast d < n to ważna optymalizacja: wystarczy sprawdzać dzielniki do pierwiastka z liczby, bo większy dzielnik zawsze ma mniejszego partnera. Przy stu liczbach różnicy nie widać, przy milionie — ogromną.

ZAD 5Zgadywanka★★★

Program losuje liczbę od 1 do 100, a następnie sam ją odgaduje metodą połowienia przedziału: zaczyna od środka, a po każdej próbie zawęża zakres w zależności od tego, czy strzał był za wysoki, czy za niski. Wypisz każdą próbę i licznik prób. Ile najwyżej prób potrzeba? Sprawdź eksperymentalnie, uruchamiając program kilkanaście razy.

pokaż rozwiązanie
const szukana = Math.floor(Math.random() * 100) + 1;

let dol = 1;
let gora = 100;
let proby = 0;
let strzal: number;

do {
  strzal = Math.floor((dol + gora) / 2);
  proby++;

  if (strzal < szukana) {
    console.log(`Próba ${proby}: ${strzal} — za mało`);
    dol = strzal + 1;
  } else if (strzal > szukana) {
    console.log(`Próba ${proby}: ${strzal} — za dużo`);
    gora = strzal - 1;
  }
} while (strzal !== szukana);

console.log(`Trafiono: ${szukana} w ${proby} próbach`);

Najwyżej siedem prób — bo każde połowienie zmniejsza przedział dwukrotnie, a 2⁷ = 128 > 100. To algorytm wyszukiwania binarnego, jeden z najważniejszych w informatyce; wrócimy do niego przy sortowaniu.

PODSUMOWANIE

Co trzeba zapamiętać

  • for gdy znasz liczbę obiegów lub potrzebujesz indeksu; warunek i < length.
  • for…of to domyślny wybór przy tablicach i tekstach.
  • while gdy liczba obiegów zależy od danych; do…while gdy blok musi wykonać się raz.
  • Każda pętla while musi mieć instrukcję zbliżającą warunek do fałszu.
  • break przerywa pętlę, continue pomija jeden obieg; w zagnieżdżonych działają tylko na swoim poziomie.
  • Akumulatory i liczniki deklaruj przed pętlą, nie w środku.
  • Nie modyfikuj tablicy, po której właśnie przechodzisz.

W następnej lekcji

Zaczynamy blok o funkcjach. Cały kod, który do tej pory pisałeś w jednym ciągu, da się zamknąć w nazwane, wielokrotnie używane bloki z określonymi typami wejścia i wyjścia.