Przetwarzanie tablic — od danych do rankingu

Wybierz aktywnych graczy, posortuj punkty i zbuduj raport. Naucz się dobierać metodę do pytania, zamiast każdą operację wciskać w forEach.

CEL LEKCJI

Po tej lekcji potrafisz

  • Dobierzesz map, filter, find, some, every i reduce.
  • Posortujesz liczby oraz obiekty z poprawnym komparatorem.
  • Rozpoznasz metody zmieniające tablicę i płytkie kopie.
WPROWADZENIE

Kto awansował do finału?

Masz wyniki wszystkich osób, ale raport ma pokazać tylko aktywnych finalistów, od najlepszego. Podziel pracę na kroki: wybór, uporządkowanie, opis. Każdy krok będzie miał czytelny wynik.

POZNAJ SKŁADNIĘ

Małe kroki, konkretne efekty

Wybór metody zależy od wyniku

map tworzy nową wartość dla każdego elementu, filter wybiera elementy, find zwraca pierwszy pasujący lub undefined. some pyta „czy którykolwiek?”, every „czy wszystkie?”.

src/index.ts — osobny przykład
const punkty = [20, 50, 80];
console.log(punkty.map(p => p + 5).join(","));
console.log(punkty.filter(p => p >= 50).join(","));
console.log(punkty.find(p => p >= 50));
console.log(punkty.some(p => p === 80), punkty.every(p => p >= 50));
wynik w konsoli
25,55,85
50,80
50
true false

Dla pustej tablicy some daje false, every true. Nie ma elementu łamiącego warunek. Jeśli potrzebujesz przynajmniej jednej osoby, dodaj sprawdzenie length > 0.

reduce zbiera wynik, sort zmienia kolejność

reduce przekazuje akumulator i kolejny element do callbacku. Wartość początkowa obsługuje także pustą tablicę. sort bez komparatora porównuje tekstowe reprezentacje, więc do liczb podaj (a, b) => a – b.

src/index.ts — osobny przykład
const liczby = [10, 2, 30];
const suma = liczby.reduce((razem, n) => razem + n, 0);
const rosnaco = [...liczby].sort((a, b) => a - b);
console.log(suma, rosnaco.join(","));
console.log(liczby.join(","));
wynik w konsoli
42 2,10,30
10,2,30

Ujemny wynik komparatora oznacza, że a powinno być przed b; dodatni — odwrotnie, a zero równorzędność sortowania. Spread zabezpiecza kolejność oryginalnej tablicy.

PRZYKŁAD Z OMÓWIENIEM

Ranking finalistów

Remis punktowy rozstrzygamy alfabetycznie po nicku. Dla danych ćwiczeniowych używamy nicków bez polskich znaków.

src/index.ts — osobny przykład
interface Gracz { nick: string; punkty: number; aktywny: boolean }
const gracze: Gracz[] = [
  { nick: "Neo", punkty: 70, aktywny: true },
  { nick: "Ala", punkty: 90, aktywny: true },
  { nick: "Max", punkty: 99, aktywny: false },
  { nick: "Ola", punkty: 70, aktywny: true }
];
const finalisci = gracze
  .filter(g => g.aktywny && g.punkty >= 50)
  .sort((a, b) => b.punkty - a.punkty || a.nick.localeCompare(b.nick, "pl"));
finalisci.forEach((g, i) => console.log((i + 1) + ". " + g.nick + " " + g.punkty));
console.log("Suma: " + finalisci.reduce((s, g) => s + g.punkty, 0));
wynik w konsoli
1. Ala 90
2. Neo 70
3. Ola 70
Suma: 230

Jak program dochodzi do wyniku

  1. filter tworzy nową tablicę, więc następujący po nim sort nie zmienia kolejności gracze.
  2. b.punkty – a.punkty daje porządek malejący.
  3. Gdy różnica wynosi zero, || uruchamia porównanie nicków. To celowy wybór przy remisie.
  4. Obiekty graczy pozostają wspólne między tablicami. Nie zmieniamy ich pól w callbackach.

Zanim przejdziesz dalej

Czy finalisci[0].punkty = 0 mogłoby zmienić obiekt w gracze?

Sprawdź swoje rozumowanie

Tak. Nowa tablica nie oznacza nowych obiektów. filter kopiuje referencje do wybranych elementów.

EKSPERYMENT

Zmień kod i zobacz, co się stanie

flatMap łączy mapowanie i spłaszczenie

Gdy jeden element ma dać kilka wyników, flatMap spłaszcza wynik o jeden poziom. Z kolei findIndex daje indeks lub -1, co trzeba sprawdzić przed usuwaniem.

src/index.ts — osobny przykład
const grupy = [["Ala", "Neo"], ["Ola"]];
console.log(grupy.flatMap(grupa => grupa).join(", "));
const lista = ["Ala", "Neo"];
const indeks = lista.findIndex(nick => nick === "Max");
if (indeks !== -1) { lista.splice(indeks, 1); }
console.log(lista.join(", "));
wynik w konsoli
Ala, Neo, Ola
Ala, Neo

splice(-1, 1) usunęłoby ostatni element — mimo braku szukanego nicku. W nowszym JavaScripcie są też toSorted, toReversed i toSpliced, które zwracają nowe tablice; wymagają odpowiedniego środowiska i biblioteki typów ES2023. W naszym ustawieniu ES2022 stosujemy kopię i poznane metody.

CZĘSTE BŁĘDY

Gdy wynik zaskakuje

Pomyłka Co się dzieje i jak to poprawić
reduce bez wartości początkowej na [] Spowoduje wyjątek. Przy sumie podaj 0.
sort liczb bez komparatora 10 może znaleźć się przed 2, bo porównanie jest tekstowe.
findIndex użyte bez kontroli -1 Może doprowadzić do usunięcia innego elementu.

JSON.stringify tylko do wypisania

W jednym z rozwiązań używamy JSON.stringify, żeby wygodnie wypisać obiekt w konsoli. To jedyne zastosowanie w tej lekcji. Zamianę danych na tekst i z powrotem, razem z walidacją, omawia lekcja 29.

ZADANIA

Teraz Twoja kolej

Rozwiązanie jest jedną z możliwości. Zanim je odkryjesz, uruchom własną wersję dla podanych danych i przynajmniej jednego przypadku brzegowego.

ZAD 1Ceny z dostawą★☆☆

Do każdej ceny [10, 20] dodaj 5 przez map. Oryginał ma pozostać bez zmian.

Mała podpowiedź

Zwróć nową liczbę.

Przykładowe rozwiązanie z wyjaśnieniem
src/index.ts — osobny przykład
const ceny = [10, 20];
console.log(ceny.map(c => c + 5).join(","));
console.log(ceny.join(","));
wynik w konsoli
15,25
10,20

map tworzy nową tablicę wyników.

ZAD 2Czy drużyna gotowa?★★☆

Funkcja gotowa ma zwracać true tylko dla niepustej listy samych true. Sprawdź [], [true, true], [true, false].

Mała podpowiedź

Połącz length i every.

Przykładowe rozwiązanie z wyjaśnieniem
src/index.ts — osobny przykład
function gotowa(gracze: boolean[]): boolean { return gracze.length > 0 && gracze.every(g => g); }
console.log(gotowa([]), gotowa([true, true]), gotowa([true, false]));
wynik w konsoli
false true false

Samo every([]) dawałoby true, co nie odpowiada regule zadania.

ZAD 3Podium★★☆

Z [30, 100, 20, 80] wybierz trzy największe wyniki malejąco. Nie zmieniaj wejścia.

Mała podpowiedź

Kopia, sort, slice(0, 3).

Przykładowe rozwiązanie z wyjaśnieniem
src/index.ts — osobny przykład
const wyniki = [30, 100, 20, 80];
const podium = [...wyniki].sort((a, b) => b - a).slice(0, 3);
console.log(podium.join(","));
console.log(wyniki.join(","));
wynik w konsoli
100,80,30
30,100,20,80

slice nie obejmuje indeksu końcowego i działa też dla krótszej listy.

ZAD 4Raport koszyka★★★

Produkty mają cena i sztuki. Oblicz łączną liczbę sztuk i koszt dla (10, 2) oraz (5, 3), używając reduce. Sprawdź pusty koszyk.

Mała podpowiedź

Akumulator może być obiektem z dwoma polami.

Przykładowe rozwiązanie z wyjaśnieniem
src/index.ts — osobny przykład
type Pozycja = { cena: number; sztuki: number };
function raport(koszyk: Pozycja[]) {
  return koszyk.reduce((s, p) => ({ sztuki: s.sztuki + p.sztuki, koszt: s.koszt + p.cena * p.sztuki }), { sztuki: 0, koszt: 0 });
}
console.log(JSON.stringify(raport([{ cena: 10, sztuki: 2 }, { cena: 5, sztuki: 3 }])));
console.log(JSON.stringify(raport([])));
wynik w konsoli
{"sztuki":5,"koszt":35}
{"sztuki":0,"koszt":0}

JSON.stringify pokazuje pola wyniku jako tekst. Szczegóły JSON-u poznasz w lekcji 29.

PODSUMOWANIE

Co zabierasz do następnej lekcji

  • Wybierz metodę po rodzaju potrzebnego wyniku.
  • Pilnuj pustych list i braku dopasowania.
  • Nowa tablica może nadal zawierać te same obiekty.