Kurs JavaScript i TypeScript · Moduł 2: Funkcje, tablice i obiekty

Iteracja po tablicach (forEach, map, filter, reduce)

13 min czytania
W tej lekcji9

W Parku Jurajskim dane o dinozaurach przyrastają szybciej niż same dinozaury - gatunek, wymiary, dieta, poziom agresji, terminy badań weterynaryjnych. Kiedy to wszystko wyląduje w jednej tablicy, pojedynczy wpis przestaje nas obchodzić. Chcemy odwiedzić każdy element, przerobić go na coś innego, wyłowić tylko te okazy, które wymagają reakcji, albo sprowadzić całe stado do jednej liczby na tablicy w centrum kontroli. JavaScript ma na to osobną rodzinę metod iteracyjnych i każda z nich ma inną specjalność. Trafny wybór to różnica między czytelnym raportem bezpieczeństwa a kodem, do którego nikt nie chce wracać.

Metoda forEach

Metoda forEach uruchamia funkcję raz dla każdego elementu tablicy. To najprostszy sposób iteracji - sięgamy po niego wtedy, gdy chcemy z elementami coś zrobić: wypisać je, zapisać do dziennika, wysłać do stacji monitoringu. Nowa tablica nie jest nam wtedy do niczego potrzebna. Funkcja, którą przekazujemy, dostaje bieżący element, a gdy zajdzie taka potrzeba - również jego indeks i całą tablicę.

1// Tablica zawierająca obiekty reprezentujące dinozaury w naszym parku
2const dinosaurs = [
3  { name: "Tyrannosaurus", diet: "carnivore", aggressionLevel: 10, dangerous: true },
4  { name: "Triceratops", diet: "herbivore", aggressionLevel: 4, dangerous: false },
5  { name: "Velociraptor", diet: "carnivore", aggressionLevel: 9, dangerous: true },
6  { name: "Stegosaurus", diet: "herbivore", aggressionLevel: 2, dangerous: false },
7  { name: "Brachiosaurus", diet: "herbivore", aggressionLevel: 1, dangerous: false }
8];
9
10// Używamy forEach do wyświetlenia nazw wszystkich dinozaurów
11console.log("Lista dinozaurów w parku:");
12dinosaurs.forEach(function(dino) {
13  console.log(dino.name);
14});
15
16// Możemy również użyć funkcji strzałkowej (arrow function) dla bardziej zwięzłego zapisu
17console.log("Lista dinozaurów z poziomem agresji:");
18dinosaurs.forEach(dino => {
19  console.log(`${dino.name}: Poziom agresji ${dino.aggressionLevel}/10`);
20});

Metoda forEach nie oddaje nam niczego - a dokładniej zwraca undefined - dlatego używamy jej tylko wtedy, gdy zależy nam na samym działaniu na elementach, a nie na wyniku do dalszego przetworzenia. Warto od razu wiedzieć, czego forEach nie potrafi: nie da się jej zatrzymać w połowie. break w środku funkcji zwrotnej jest niedozwolone, a return kończy wyłącznie bieżące wywołanie i przechodzimy prosto do kolejnego dinozaura. Gdy naprawdę potrzebujemy przerwać przebieg wcześniej, robotę biorą na siebie find, some, every albo klasyczna pętla for. Przyjrzyj się jeszcze polu dangerous w naszych kartotekach - to flaga, którą regulamin parku podnosi drapieżnikom pod wzmożonym nadzorem. Wrócimy do niej pod koniec lekcji.

Metoda map

Metoda map tworzy nową tablicę z wynikami wywołania przekazanej funkcji dla każdego elementu tablicy wejściowej. Jest niezwykle przydatna wszędzie tam, gdzie przekształcamy dane z jednej postaci na drugą - z pełnych kartotek na zwykłą listę nazw albo na krótkie podsumowania bezpieczeństwa, które strażnicy faktycznie zdążą przeczytać przed obchodem.

1// Używamy map do stworzenia nowej tablicy zawierającej tylko nazwy dinozaurów
2const dinoNames = dinosaurs.map(dino => dino.name);
3console.log("Nazwy dinozaurów:", dinoNames);
4// Wynik: ["Tyrannosaurus", "Triceratops", "Velociraptor", "Stegosaurus", "Brachiosaurus"]
5
6// Możemy też tworzyć bardziej złożone transformacje
7const dinoSummaries = dinosaurs.map(dino => {
8  return {
9    name: dino.name,
10    dangerLevel: dino.diet === "carnivore" ? "Wysoki" : "Niski",
11    warning: dino.aggressionLevel > 7 ? "Zachować szczególną ostrożność!" : "Standardowe procedury bezpieczeństwa"
12  };
13});
14
15console.log("Podsumowanie bezpieczeństwa dinozaurów:", dinoSummaries);

W przeciwieństwie do forEach, metoda map zawsze oddaje nową tablicę o tej samej długości co oryginalna, wypełnioną przekształconymi elementami. I na tym właśnie polega kluczowa różnica między nimi: map() zwraca nową tablicę, a forEach() zwraca undefined. To nie jest kwestia wydajności - map() nie jest szybsze, obie metody odwiedzają dokładnie tyle samo elementów, każdy po jednym razie. To także nie jest kwestia przerywania: ani forEach(), ani map() nie da się zatrzymać w trakcie, więc twierdzenie, że tylko jedną z nich można zatrzymać, jest po prostu nieprawdziwe. Nie działają też identycznie - wywołanie forEach w nadziei na odzyskanie tablicy zostawi Cię z undefined w ręku. Po map sięgamy, gdy potrzebujemy wyniku, po forEach - gdy liczy się wyłącznie efekt uboczny.

Metoda filter

Metoda filter tworzy nową tablicę zawierającą tylko te elementy, które spełniają zadany warunek. To idealne narzędzie, gdy z większej kolekcji trzeba wyodrębnić podzbiór: mięsożercy spośród wszystkich dinozaurów, wybiegi do inspekcji spośród wszystkich wybiegów, kartoteki do przekazania weterynarzowi spośród wszystkich kartotek.

1// Filtrujemy, aby uzyskać tylko dinozaury mięsożerne
2const carnivores = dinosaurs.filter(dino => dino.diet === "carnivore");
3console.log("Mięsożerne dinozaury:", carnivores);
4
5// Możemy łączyć warunki, aby tworzyć bardziej złożone filtry
6const dangerousDinos = dinosaurs.filter(dino => {
7  return dino.diet === "carnivore" && dino.aggressionLevel > 7;
8});
9console.log("Najbardziej niebezpieczne dinozaury:", dangerousDinos);
10
11// Możemy też łączyć filter z map, aby najpierw odfiltrować, a potem przekształcić dane
12const safetyWarnings = dinosaurs
13  .filter(dino => dino.aggressionLevel > 5)
14  .map(dino => `UWAGA: ${dino.name} ma wysoki poziom agresji!`);
15
16console.log("Ostrzeżenia bezpieczeństwa:", safetyWarnings);

Metoda filter zwraca nową tablicę, która bywa krótsza od oryginalnej, bo trafiają do niej wyłącznie elementy, które przeszły przez "filtr" - czyli te, dla których funkcja testująca zwróciła true. Sama tablica dinosaurs wychodzi z tego bez zadraśnięcia: podobnie jak map, filter nigdy nie modyfikuje kolekcji, na której ją wywołujemy, a jedynie o niej raportuje. Zapamiętaj też układ ostatniego przykładu, bo za chwilę okaże się kluczowy: najpierw filtrowanie, dopiero potem transformacja. Ta kolejność nie jest kwestią gustu i zaraz zobaczysz dlaczego.

Metoda reduce

Metoda reduce jest najpotężniejsza z metod iteracyjnych, ale też najbardziej wymagająca. Służy do redukcji tablicy do pojedynczej wartości, na przykład sumy. Wykonuje funkcję na każdym elemencie, przekazując dalej wynik z poprzedniej iteracji: w każdym kroku dostajesz to, co udało się nazbierać do tej pory, oraz bieżący element, a oddajesz nową wartość akumulatora.

1// Obliczamy sumę poziomów agresji wszystkich dinozaurów
2const totalAggressionLevel = dinosaurs.reduce((total, dino) => {
3  return total + dino.aggressionLevel;
4}, 0); // 0 to wartość początkowa
5
6console.log("Całkowity poziom agresji w parku:", totalAggressionLevel);
7
8// Możemy użyć reduce do tworzenia bardziej złożonych struktur, np. obiektu grupującego dinozaury według diety
9const dinosByDiet = dinosaurs.reduce((groups, dino) => {
10  // Jeśli grupa dla tej diety jeszcze nie istnieje, tworzymy ją
11  if (!groups[dino.diet]) {
12    groups[dino.diet] = [];
13  }
14
15  // Dodajemy dinozaura do odpowiedniej grupy
16  groups[dino.diet].push(dino.name);
17
18  return groups;
19}, {}); // {} to wartość początkowa (pusty obiekt)
20
21console.log("Dinozaury pogrupowane według diety:", dinosByDiet);
22// Wynik: { "carnivore": ["Tyrannosaurus", "Velociraptor"], "herbivore": ["Triceratops", "Stegosaurus", "Brachiosaurus"] }

Metoda reduce przyjmuje dwa parametry:

  1. Funkcję redukcji, która sama przyjmuje co najmniej dwa parametry:
    • Akumulator: wartość zwrócona z poprzedniej iteracji (lub wartość początkowa)
    • Bieżący element: aktualnie przetwarzany element tablicy
  2. Wartość początkową (opcjonalną, ale zdecydowanie zalecaną)

Nazwa tej metody bywa myląca, więc uporządkujmy, do czego reduce() nie służy. Nie usuwa elementów z tablicy - obie powyższe operacje przeczytały nasze kartoteki i zostawiły je w spokoju. Nie zmniejsza też rozmiaru tablicy: po wykonaniu obu wywołań dinosaurs nadal liczy pięć wpisów, co do jednego. Nie sortuje niczego malejąco - porządkowaniem elementów zajmuje się sort(), którą poznasz za moment. Słowo "redukcja" oznacza tu dokładnie jedno: sprowadzenie całej tablicy do pojedynczej wartości, czy będzie to liczba, tekst, czy obiekt grupujący pokroju dinosByDiet.

Metoda find

Metoda find zwraca pierwszy element tablicy, który spełnia podany warunek. Po nią sięgamy wtedy, gdy szukamy konkretnej kartoteki, a nie całej listy: strażnik, który prosi "otwórz mi teczkę Stegozaura", nie oczekuje tablicy, tylko jednego dinozaura.

1// Szukamy dinozaura o określonej nazwie
2const stegosaurus = dinosaurs.find(dino => dino.name === "Stegosaurus");
3console.log("Informacje o Stegosaurusie:", stegosaurus);
4
5// Szukamy pierwszego dinozaura mięsożernego o agresji większej niż 8
6const dangerousCarnivore = dinosaurs.find(dino => {
7  return dino.diet === "carnivore" && dino.aggressionLevel > 8;
8});
9console.log("Pierwszy niebezpieczny mięsożerca:", dangerousCarnivore); // Tyrannosaurus
10
11// findIndex działa tak samo, ale zwraca pozycję zamiast kartoteki
12const stegoIndex = dinosaurs.findIndex(dino => dino.name === "Stegosaurus");
13console.log("Pozycja Stegosaurusa:", stegoIndex); // 3

W przeciwieństwie do filter, który zwraca tablicę wszystkich pasujących elementów, find oddaje tylko pierwsze trafienie i natychmiast przerywa przeszukiwanie - pozostałe dinozaury nie zostaną nawet obejrzane. Jeśli nic nie pasuje, dostajemy undefined. Jej bliźniacza siostra findIndex kieruje się tą samą logiką, ale zamiast elementu podaje jego indeks, a przy braku dopasowania zwraca -1. Bywa to nieocenione, gdy zaraz potem trzeba podmienić kartotekę leżącą na tej pozycji.

Metody some i every

Metody some i every zwracają wartość boolowską i służą do sprawdzania, czy elementy tablicy spełniają określone kryteria. Żadna z nich nie buduje nowej tablicy ani nie oddaje elementów - odpowiadają po prostu true albo false na pytanie o całą kolekcję, co jest dokładnie tym, czego potrzebuje alarm albo kontrola bezpieczeństwa przed otwarciem parku.

some

Metoda some sprawdza, czy przynajmniej jeden element tablicy spełnia podany warunek. Gdy tylko natrafi na pasujący element, kończy przebieg i zwraca true - dalsze szukanie niczego by już nie zmieniło.

1// Sprawdzamy, czy w parku są jakieś niebezpieczne dinozaury
2const hasHighAggressionDinos = dinosaurs.some(dino => dino.aggressionLevel > 8);
3console.log("Czy park ma dinozaury o wysokiej agresji?", hasHighAggressionDinos); // true
4
5// Sprawdzamy, czy w parku są jakieś dinozaury wszystkożerne
6const hasOmnivores = dinosaurs.some(dino => dino.diet === "omnivore");
7console.log("Czy park ma dinozaury wszystkożerne?", hasOmnivores); // false

Pierwsze wywołanie zwraca true, bo Tyranozaur przekracza próg już przy pierwszym sprawdzanym elemencie i dalej nie ma po co iść. Drugie musi przejść całą listę, zanim wyda werdykt: żadna kartoteka nie ma diety "omnivore", więc odpowiedź brzmi false. To właśnie ta rodzina metod potrafi przerwać przebieg w połowie - o forEach i map pamiętaj, że zawsze idą do samego końca tablicy.

every

Metoda every sprawdza, czy wszystkie elementy tablicy spełniają podany warunek. Działa symetrycznie do some: zatrzymuje się na pierwszym elemencie, który obleje test, i od razu zwraca false.

1// Sprawdzamy, czy wszystkie dinozaury mają przypisany poziom agresji
2const allHaveAggressionLevel = dinosaurs.every(dino => dino.aggressionLevel !== undefined);
3console.log("Czy wszystkie dinozaury mają przypisany poziom agresji?", allHaveAggressionLevel); // true
4
5// Sprawdzamy, czy wszystkie dinozaury są roślinożerne
6const allHerbivores = dinosaurs.every(dino => dino.diet === "herbivore");
7console.log("Czy wszystkie dinozaury są roślinożerne?", allHerbivores); // false

Pierwsza kontrola wypada pomyślnie: każda z pięciu kartotek ma uzupełnione pole aggressionLevel, więc every przechodzi całą tablicę i zwraca true. Druga wykłada się już na pierwszym wpisie - Tyranozaur jest mięsożercą - i zwraca false, nie zaglądając do pozostałych. Najłatwiej zapamiętać tę parę jako dwa pytania zadawane na głos: some pyta "czy jest przynajmniej jeden?", a every pyta "czy wszystkie?".

Metoda sort

Metoda sort porządkuje elementy tablicy i zwraca posortowaną tablicę. Domyślnie porównuje je jak ciągi znaków, co przy liczbach potrafi zaskoczyć - tablica [10, 9, 100] wychodzi z takiego sortowania jako [10, 100, 9], bo znak po znaku "1" wypada przed "9". Właśnie dlatego niemal zawsze przekazujemy jej własną funkcję porównującą.

1// Sortujemy dinozaury według nazwy (alfabetycznie)
2const dinosSortedByName = [...dinosaurs].sort((a, b) => {
3  return a.name.localeCompare(b.name);
4});
5console.log("Dinozaury posortowane alfabetycznie:", dinosSortedByName);
6
7// Sortujemy dinozaury według poziomu agresji (od najmniej do najbardziej agresywnych)
8const dinosSortedByAggression = [...dinosaurs].sort((a, b) => {
9  return a.aggressionLevel - b.aggressionLevel;
10});
11console.log("Dinozaury posortowane według agresji (rosnąco):", dinosSortedByAggression);
12
13// Sortujemy dinozaury według poziomu agresji (od najbardziej do najmniej agresywnych)
14const mostDangerousFirst = [...dinosaurs].sort((a, b) => {
15  return b.aggressionLevel - a.aggressionLevel;
16});
17console.log("Dinozaury posortowane według agresji (malejąco):", mostDangerousFirst);

Kopię tablicy tworzymy operatorem spread (...) nie bez powodu: sort modyfikuje oryginał, a my chcemy zachować pierwotną kolejność rejestru parku. To wielki wyjątek wśród poznanych dotąd metod - map, filter i reduce zostawiają źródło nietknięte, sort nie. O kierunku decyduje funkcja porównująca: wynik ujemny stawia a przed b, dlatego a.aggressionLevel - b.aggressionLevel układa okazy rosnąco, a zamiana operandów miejscami daje porządek malejący. I to sort, wyłącznie sort, odpowiada w JavaScripcie za sortowanie rosnące i malejące.

Łączenie metod

Jedną z najpotężniejszych cech tych metod jest możliwość łączenia ich w "łańcuchy", dzięki czemu złożoną operację na danych zapisujemy jako jedno czytelne wyrażenie. Działa to dlatego, że filter, map i sort oddają tablicę, więc kolejną metodę wywołujemy wprost na wyniku poprzedniej. Najczęstszy wzorzec w parku składa się z dwóch kroków: filtrowania, a zaraz po nim transformacji.

1// Źródło, potem filtrowanie, potem transformacja
2const dangerousNames = dinosaurs
3  .filter(d => d.dangerous)   // 1. zostawiamy tylko kartoteki z flagą dangerous
4  .map(d => d.name);          // 2. z każdej ocalałej kartoteki bierzemy samą nazwę
5
6console.log("Nazwy niebezpiecznych dinozaurów:", dangerousNames);
7// Wynik: ["Tyrannosaurus", "Velociraptor"]

Ten łańcuch czytamy od lewej do prawej, a kolejność jego ogniw jest zawsze taka sama: najpierw źródło danych dinosaurs, potem filtrowanie .filter(d => d.dangerous), a na końcu transformacja .map(d => d.name). Ta kolejność nie podlega negocjacjom. Gdyby zamienić dwa ostatnie ogniwa miejscami, map oddałoby tablicę zwykłych napisów, a filter szukałoby potem właściwości dangerous na tekście - wszędzie wypadłoby undefined i w wyniku dostalibyśmy pustą tablicę. Filtrujemy, dopóki mamy jeszcze pełne obiekty, przekształcamy dopiero potem. Gdy ten odruch wejdzie Ci w krew, łańcuch możesz wydłużać dowolnie i wplatać w niego sort albo reduce.

1// Znajdźmy nazwy wszystkich dinozaurów roślinożernych, posortowane alfabetycznie
2const sortedHerbivoreNames = dinosaurs
3  .filter(dino => dino.diet === "herbivore")  // Najpierw filtrujemy
4  .map(dino => dino.name)                     // Potem transformujemy do tablicy nazw
5  .sort();                                    // Na końcu sortujemy alfabetycznie
6
7console.log("Roślinożercy (alfabetycznie):", sortedHerbivoreNames);
8// Wynik: ["Brachiosaurus", "Stegosaurus", "Triceratops"]
9
10// Obliczmy średni poziom agresji mięsożerców
11const averageCarnivoreAggression = dinosaurs
12  .filter(dino => dino.diet === "carnivore")   // Najpierw filtrujemy tylko mięsożerców
13  .reduce((sum, dino, index, array) => {       // Potem redukujemy do średniej
14    sum += dino.aggressionLevel;               // Dodajemy poziom agresji do sumy
15
16    // Jeśli to ostatni element, dzielimy przez liczbę elementów
17    if (index === array.length - 1) {
18      return sum / array.length;
19    }
20
21    return sum;                               // Zwracamy sumę częściową
22  }, 0);
23
24console.log("Średni poziom agresji mięsożerców:", averageCarnivoreAggression); // 9.5

Drugi przykład pokazuje, jak elastyczna potrafi być reduce. Funkcja redukcji dostaje nie tylko akumulator i bieżący element, ale też indeks oraz całą przetwarzaną tablicę, dzięki czemu potrafimy rozpoznać ostatnią iterację i w odpowiednim momencie podzielić zebraną sumę. Mięsożerców mamy dwóch, ich poziomy agresji to 10 i 9, więc na wyjściu dostajemy 9.5 - jedną liczbę wyciśniętą z całej tablicy kartotek, czyli dokładnie to, do czego reduce została stworzona.

Podsumowanie

Metody iteracyjne tablic w JavaScript są nieocenionym narzędziem przy pracy z kolekcjami danych. Każda ma swoją specjalizację:

  • forEach: gdy chcemy wykonać jakąś operację na każdym elemencie, bez tworzenia nowej tablicy (zwraca undefined)
  • map: gdy chcemy przekształcić każdy element tablicy w coś innego (zwraca nową tablicę)
  • filter: gdy chcemy wybrać podgrupę elementów spełniających określone kryteria
  • reduce: gdy chcemy zredukować tablicę do pojedynczej wartości, na przykład sumy, albo do nowej struktury
  • find: gdy szukamy pierwszego elementu spełniającego określone kryterium
  • some/every: gdy chcemy sprawdzić, czy pewne/wszystkie elementy spełniają określone kryterium
  • sort: gdy chcemy uporządkować elementy tablicy

"Metody iteracyjne to poranna odprawa w parku" - mawia Dr. Rex. "forEach przechodzi codzienną listę kontrolną, filter wyławia tylko te okazy, które wymagają uwagi, map przerabia je na raport, a reduce daje centrum kontroli jedną liczbę, na której da się oprzeć decyzję. Każde narzędzie ma swoje zadanie!"

W naszym Parku Jurajskim te metody są niezbędne do efektywnego zarządzania danymi o dinozaurach, monitorowania bezpieczeństwa i podejmowania decyzji opartych na danych. Zapamiętaj przede wszystkim jedno: metodę dobieramy według tego, co chcemy dostać - efekt uboczny, nową tablicę, podzbiór czy pojedynczą wartość - a reszta kodu napisze się już niemal sama.

Kod do tej lekcji: index.js
1// Iteracje po tablicach - Park Jurajski
2console.log("Zaawansowane Iteracje");
3console.log("forEach, map, filter, reduce\n");
4
5// ===========================================
6// DANE TESTOWE
7// ===========================================
8const dinosaurs = [
9  { id: 1, name: "Rexy", species: "T-Rex", age: 8, weight: 8000, sector: "A", health: 95, carnivore: true, fed: true },
10  { id: 2, name: "Blue", species: "Velociraptor", age: 5, weight: 80, sector: "B", health: 100, carnivore: true, fed: false },
11  { id: 3, name: "Charlie", species: "Velociraptor", age: 5, weight: 80, sector: "B", health: 85, carnivore: true, fed: false },
12  { id: 4, name: "Delta", species: "Velociraptor", age: 5, weight: 80, sector: "B", health: 45, carnivore: true, fed: true },
13  { id: 5, name: "Trike", species: "Triceratops", age: 7, weight: 9000, sector: "C", health: 70, carnivore: false, fed: true },
14  { id: 6, name: "Stego", species: "Stegosaurus", age: 6, weight: 5000, sector: "C", health: 92, carnivore: false, fed: false },
15  { id: 7, name: "Brachio", species: "Brachiosaurus", age: 10, weight: 50000, sector: "D", health: 88, carnivore: false, fed: true }
16];
17
18// ===========================================
19// 1. forEach() - KLASYCZNA ITERACJA
20// ===========================================
21console.log("=== 1. forEach() - Iteracja bez zwracania ===");
22
23console.log("\nLista wszystkich dinozaurów:");
24dinosaurs.forEach((dino, index) => {
25  const icon = dino.carnivore ? "mięsożerca" : "roślinożerca";
26  console.log(`  ${index + 1}. ${icon} ${dino.name} (${dino.species})`);
27});
28
29// forEach z efektami ubocznymi
30let totalWeight = 0;
31dinosaurs.forEach(dino => {
32  totalWeight += dino.weight;
33});
34console.log(`\nŁączna waga: ${totalWeight}kg`);
35
36// ===========================================
37// 2. map() - TRANSFORMACJA DANYCH
38// ===========================================
39console.log("\n=== 2. map() - Transformacja każdego elementu ===");
40
41// Przykład 1: Proste wyciągnięcie wartości
42const names = dinosaurs.map(dino => dino.name);
43console.log("\nSame imiona:", names.join(", "));
44
45// Przykład 2: Tworzenie nowych obiektów
46const summaries = dinosaurs.map(dino => ({
47  name: dino.name,
48  type: dino.carnivore ? "Mięsożerca" : "Roślinożerca",
49  status: dino.health >= 80 ? "Zdrowy" : "Wymaga uwagi"
50}));
51
52console.log("\nPodsumowania:");
53summaries.forEach(s => {
54  console.log(`  ${s.name}: ${s.type}, Status: ${s.status}`);
55});
56
57// Przykład 3: Kalkulacje
58const healthPercentages = dinosaurs.map(dino => {
59  const healthStatus = dino.health >= 90 ? "*" : dino.health >= 70 ? "OK" : "!";
60  return `${healthStatus} ${dino.name}: ${dino.health}%`;
61});
62
63console.log("\nPoziomy zdrowia:");
64healthPercentages.forEach(h => console.log(`  ${h}`));
65
66// ===========================================
67// 3. filter() - FILTROWANIE
68// ===========================================
69console.log("\n=== 3. filter() - Wybieranie elementów ===");
70
71// Przykład 1: Jeden warunek
72const carnivores = dinosaurs.filter(dino => dino.carnivore);
73console.log(`\nMięsożercy (${carnivores.length}):`, carnivores.map(d => d.name).join(", "));
74
75// Przykład 2: Wiele warunków
76const healthyCarnivores = dinosaurs.filter(dino =>
77  dino.carnivore && dino.health >= 80
78);
79console.log(`Zdrowi mięsożercy (${healthyCarnivores.length}):`, healthyCarnivores.map(d => d.name).join(", "));
80
81// Przykład 3: Złożone warunki
82const needsAttention = dinosaurs.filter(dino =>
83  dino.health < 80 || !dino.fed
84);
85console.log(`\nWymagają uwagi (${needsAttention.length}):`);
86needsAttention.forEach(dino => {
87  const reasons = [];
88  if (dino.health < 80) reasons.push(`zdrowie: ${dino.health}%`);
89  if (!dino.fed) reasons.push("głodny");
90  console.log(`  ${dino.name}: ${reasons.join(", ")}`);
91});
92
93// Przykład 4: Filtrowanie po sektorze
94const sectorB = dinosaurs.filter(dino => dino.sector === "B");
95console.log(`\nSektor B (${sectorB.length}):`, sectorB.map(d => d.name).join(", "));
96
97// ===========================================
98// 4. reduce() - AGREGACJA
99// ===========================================
100console.log("\n=== 4. reduce() - Agregacja danych ===");
101
102// Przykład 1: Suma
103const totalHealthPoints = dinosaurs.reduce((sum, dino) => sum + dino.health, 0);
104console.log(`\nSuma punktów zdrowia: ${totalHealthPoints}`);
105
106// Przykład 2: Średnia
107const avgHealth = totalHealthPoints / dinosaurs.length;
108console.log(`Średnie zdrowie: ${avgHealth.toFixed(1)}%`);
109
110// Przykład 3: Znajdowanie max/min
111const oldest = dinosaurs.reduce((max, dino) =>
112  dino.age > max.age ? dino : max
113);
114console.log(`\nNajstarszy: ${oldest.name} (${oldest.age} lat)`);
115
116const youngest = dinosaurs.reduce((min, dino) =>
117  dino.age < min.age ? dino : min
118);
119console.log(`Najmłodszy: ${youngest.name} (${youngest.age} lat)`);
120
121// Przykład 4: Grupowanie
122const bySector = dinosaurs.reduce((acc, dino) => {
123  if (!acc[dino.sector]) acc[dino.sector] = [];
124  acc[dino.sector].push(dino.name);
125  return acc;
126}, {});
127
128console.log("\nGrupowanie po sektorze:");
129Object.entries(bySector).sort().forEach(([sector, dinos]) => {
130  console.log(`  Sektor ${sector}: ${dinos.join(", ")}`);
131});
132
133// Przykład 5: Liczenie wystąpień
134const speciesCount = dinosaurs.reduce((acc, dino) => {
135  acc[dino.species] = (acc[dino.species] || 0) + 1;
136  return acc;
137}, {});
138
139console.log("\nLiczba według gatunku:");
140Object.entries(speciesCount).forEach(([species, count]) => {
141  console.log(`  ${species}: ${count}`);
142});
143
144// Przykład 6: Budowanie obiektu statystyk
145const stats = dinosaurs.reduce((acc, dino) => {
146  acc.totalDinos++;
147  acc.totalWeight += dino.weight;
148  acc.totalAge += dino.age;
149  if (dino.carnivore) acc.carnivores++;
150  if (dino.health >= 90) acc.veryHealthy++;
151  return acc;
152}, { totalDinos: 0, totalWeight: 0, totalAge: 0, carnivores: 0, veryHealthy: 0 });
153
154console.log("\nStatystyki zbiorcze:");
155console.log(`  Łącznie dinozaurów: ${stats.totalDinos}`);
156console.log(`  Łączna waga: ${stats.totalWeight}kg`);
157console.log(`  Średni wiek: ${(stats.totalAge / stats.totalDinos).toFixed(1)} lat`);
158console.log(`  Mięsożercy: ${stats.carnivores}`);
159console.log(`  Bardzo zdrowe (≥90%): ${stats.veryHealthy}`);
160
161// ===========================================
162// 5. ŁAŃCUCHOWANIE METOD
163// ===========================================
164console.log("\n=== 5. Łańcuchowanie metod ===");
165
166// Przykład 1: filter + map + sort
167const topHealthyCarnivores = dinosaurs
168  .filter(d => d.carnivore)
169  .filter(d => d.health >= 80)
170  .map(d => ({ name: d.name, health: d.health }))
171  .sort((a, b) => b.health - a.health)
172  .slice(0, 3);
173
174console.log("\nTOP 3 zdrowych mięsożerców:");
175topHealthyCarnivores.forEach((d, i) => {
176  console.log(`  ${i + 1}. ${d.name} - ${d.health}%`);
177});
178
179// Przykład 2: filter + map + reduce
180const heavyHerbivoreWeight = dinosaurs
181  .filter(d => !d.carnivore)
182  .filter(d => d.weight > 1000)
183  .map(d => d.weight)
184  .reduce((sum, weight) => sum + weight, 0);
185
186console.log(`\nŁączna waga ciężkich roślinożerców: ${heavyHerbivoreWeight}kg`);
187
188// Przykład 3: Złożone przetwarzanie
189const sectorReport = Object.entries(
190  dinosaurs
191    .filter(d => d.health >= 70)
192    .reduce((acc, d) => {
193      if (!acc[d.sector]) {
194        acc[d.sector] = { count: 0, avgHealth: 0, totalHealth: 0 };
195      }
196      acc[d.sector].count++;
197      acc[d.sector].totalHealth += d.health;
198      return acc;
199    }, {})
200)
201.map(([sector, data]) => ({
202  sector,
203  count: data.count,
204  avgHealth: (data.totalHealth / data.count).toFixed(1)
205}))
206.sort((a, b) => b.avgHealth - a.avgHealth);
207
208console.log("\nRaport sektorów (zdrowie ≥70%):");
209sectorReport.forEach(s => {
210  console.log(`  Sektor ${s.sector}: ${s.count} dinozaurów, średnie zdrowie: ${s.avgHealth}%`);
211});
212
213// ===========================================
214// 6. PORÓWNANIE PODEJŚĆ
215// ===========================================
216console.log("\n=== 6. Porównanie podejść ===");
217
218console.log("\nZadanie: Znajdź imiona mięsożerców w sektorze B");
219
220// Sposób 1: Pętla for (imperatywny)
221const result1 = [];
222for (let i = 0; i < dinosaurs.length; i++) {
223  if (dinosaurs[i].carnivore && dinosaurs[i].sector === "B") {
224    result1.push(dinosaurs[i].name);
225  }
226}
227console.log("For loop:", result1);
228
229// Sposób 2: forEach (częściowo funkcyjny)
230const result2 = [];
231dinosaurs.forEach(dino => {
232  if (dino.carnivore && dino.sector === "B") {
233    result2.push(dino.name);
234  }
235});
236console.log("forEach:", result2);
237
238// Sposób 3: filter + map (funkcyjny)
239const result3 = dinosaurs
240  .filter(d => d.carnivore && d.sector === "B")
241  .map(d => d.name);
242console.log("filter + map:", result3);
243
244// ===========================================
245// 7. PRZYKŁAD KOMPLEKSOWY
246// ===========================================
247console.log("\n=== 7. System raportowania - przykład kompleksowy ===");
248
249class ParkReportingSystem {
250  constructor(dinosaurs) {
251    this.dinosaurs = dinosaurs;
252  }
253
254  // Dashboard główny
255  getDashboard() {
256    const total = this.dinosaurs.length;
257
258    const healthStats = this.dinosaurs.reduce((acc, d) => {
259      if (d.health >= 90) acc.excellent++;
260      else if (d.health >= 70) acc.good++;
261      else acc.poor++;
262      acc.total += d.health;
263      return acc;
264    }, { excellent: 0, good: 0, poor: 0, total: 0 });
265
266    const feedingStats = this.dinosaurs.reduce((acc, d) => {
267      if (d.fed) acc.fed++;
268      else acc.hungry++;
269      return acc;
270    }, { fed: 0, hungry: 0 });
271
272    return {
273      totalDinosaurs: total,
274      avgHealth: (healthStats.total / total).toFixed(1),
275      healthBreakdown: {
276        excellent: healthStats.excellent,
277        good: healthStats.good,
278        poor: healthStats.poor
279      },
280      feeding: feedingStats
281    };
282  }
283
284  // Alert krytyczny
285  getCriticalAlerts() {
286    return this.dinosaurs
287      .filter(d => d.health < 50 || (d.carnivore && !d.fed))
288      .map(d => ({
289        name: d.name,
290        sector: d.sector,
291        issue: d.health < 50 ? `Krytyczne zdrowie: ${d.health}%` : "Głodny mięsożerca!",
292        priority: d.health < 50 && d.carnivore && !d.fed ? "EKSTREMALNE" : "WYSOKIE"
293      }))
294      .sort((a, b) => {
295        const priorityOrder = { "EKSTREMALNE": 0, "WYSOKIE": 1 };
296        return priorityOrder[a.priority] - priorityOrder[b.priority];
297      });
298  }
299
300  // Ranking zdrowia
301  getHealthRanking(limit = 5) {
302    return this.dinosaurs
303      .map(d => ({
304        name: d.name,
305        species: d.species,
306        health: d.health,
307        sector: d.sector
308      }))
309      .sort((a, b) => b.health - a.health)
310      .slice(0, limit);
311  }
312
313  // Analiza sektorów
314  getSectorAnalysis() {
315    const sectorData = this.dinosaurs.reduce((acc, d) => {
316      if (!acc[d.sector]) {
317        acc[d.sector] = {
318          dinosaurs: [],
319          totalHealth: 0,
320          carnivores: 0,
321          unfed: 0
322        };
323      }
324      acc[d.sector].dinosaurs.push(d.name);
325      acc[d.sector].totalHealth += d.health;
326      if (d.carnivore) acc[d.sector].carnivores++;
327      if (!d.fed) acc[d.sector].unfed++;
328      return acc;
329    }, {});
330
331    return Object.entries(sectorData)
332      .map(([sector, data]) => ({
333        sector,
334        count: data.dinosaurs.length,
335        avgHealth: (data.totalHealth / data.dinosaurs.length).toFixed(1),
336        carnivores: data.carnivores,
337        unfed: data.unfed,
338        dinosaurs: data.dinosaurs
339      }))
340      .sort((a, b) => a.sector.localeCompare(b.sector));
341  }
342
343  // Wyświetl pełny raport
344  displayFullReport() {
345    console.log("\n" + "=".repeat(70));
346    console.log("PARK JURAJSKI - RAPORT CAŁOŚCIOWY");
347    console.log("=".repeat(70));
348
349    // Dashboard
350    const dashboard = this.getDashboard();
351    console.log("\nDASHBOARD");
352    console.log(`   Łącznie dinozaurów: ${dashboard.totalDinosaurs}`);
353    console.log(`   Średnie zdrowie: ${dashboard.avgHealth}%`);
354    console.log(`   Zdrowie - Doskonałe: ${dashboard.healthBreakdown.excellent}, Dobre: ${dashboard.healthBreakdown.good}, Słabe: ${dashboard.healthBreakdown.poor}`);
355    console.log(`   Karmienie - Nakarmione: ${dashboard.feeding.fed}, Głodne: ${dashboard.feeding.hungry}`);
356
357    // Alerty
358    const alerts = this.getCriticalAlerts();
359    if (alerts.length > 0) {
360      console.log("\nALERTY KRYTYCZNE");
361      alerts.forEach((alert, i) => {
362        console.log(`   ${i + 1}. [${alert.priority}] ${alert.name} (Sektor ${alert.sector}): ${alert.issue}`);
363      });
364    } else {
365      console.log("\nBrak alertów krytycznych");
366    }
367
368    // Ranking
369    const ranking = this.getHealthRanking(5);
370    console.log("\nTOP 5 - RANKING ZDROWIA");
371    ranking.forEach((d, i) => {
372      console.log(`   ${i + 1}. ${d.name} (${d.species}) - ${d.health}% - Sektor ${d.sector}`);
373    });
374
375    // Analiza sektorów
376    const sectorAnalysis = this.getSectorAnalysis();
377    console.log("\nANALIZA SEKTORÓW");
378    sectorAnalysis.forEach(sector => {
379      console.log(`   Sektor ${sector.sector}:`);
380      console.log(`     Dinozaury: ${sector.count} - Średnie zdrowie: ${sector.avgHealth}%`);
381      console.log(`     Mięsożercy: ${sector.carnivores}, Głodne: ${sector.unfed}`);
382      console.log(`     Lista: ${sector.dinosaurs.join(", ")}`);
383    });
384  }
385}
386
387// Uruchom system raportowania
388const reportSystem = new ParkReportingSystem(dinosaurs);
389reportSystem.displayFullReport();
390
391console.log("\nGratulacje! Opanowałeś zaawansowane iteracje!");

Widzisz błąd w tej lekcji?

Sprawdź się

Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.

  1. 1. Jaka jest kluczowa różnica między map() a forEach()?

  2. 2. Do czego służy metoda reduce() na tablicy?

Zadania praktyczne w grze

  • Układanie w poziomie

    Ułóż elementy filtrowania i mapowania tablicy:

Przydatne artykuły