Kurs JavaScript i TypeScript · Moduł 2: Funkcje, tablice i obiekty
Iteracja po tablicach (forEach, map, filter, reduce)
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:
- 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
- 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); // 3W 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); // falsePierwsze 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); // falsePierwsza 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.5Drugi 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. Jaka jest kluczowa różnica między map() a forEach()?
2. Do czego służy metoda reduce() na tablicy?
Zadania praktyczne w grze
- Układanie w poziomie
Ułóż elementy filtrowania i mapowania tablicy: