Moduł4 - Zajęcia 8 - Metody tablic

Section1 Article1: Czyste funkcje

Funkcja z efektami ubocznymi to funkcja, podczas wykonywania której mogą zmieniać się lub być wykorzystywane zmienne globalne, zmieniają się wartości argumentów referencyjnych, wykonywane są operacje wejścia-wyjścia itp.

            const dirtyMultiply = (array, value) => {
                for (let i = 0; i < array.length; i += 1) {
                    array[i] = array[i] * value;
                }
            };

            const numbers = [1, 2, 3, 4, 5];
            dirtyMultiply(numbers, 2);
            // nastąpiła mutacja (zmiana) oryginalnych danych - tablicy numbers
            console.log(numbers);// [2, 4, 6, 8, 10]
          

Funkcja dirtyMultiply(array, value) mnoży każdy element tablicy array przez liczbę value. Modyfikuje ona (mutuje) oryginalną tablicę przez referencję.

Czysta funkcja (pure function) to funkcja, której wynik zależy tylko od wartości przekazanych argumentów. Przy tych samych argumentach zawsze zwraca ten sam wynik i nie ma skutków ubocznych, czyli nie zmienia wartości argumentów.

Napiszmy implementację czystej funkcji służącej do mnożenia elementów tablicy, która zwraca nową tablicę bez zmiany oryginalnej.

            const pureMultiply = (array, value) => {
                const newArray = [];

                array.forEach(element => {
                    newArray.push(element * value);
                });

                return newArray;
            };

            const numbers = [1, 2, 3, 4, 5];
            const doubledNumbers = pureMultiply(numbers, 2);

            // Nie nastąpiła mutacja oryginalnych danych
            console.log(numbers); // [1, 2, 3, 4, 5]
            // Funkcja zwróciła nową tablicę ze zmienionymi danymi
            console.log(doubledNumbers); // [2, 4, 6, 8, 10]
          

Section2 Article1: Metody iteracji po tablicy

JavaScript posiada metody tablicowe, które pochodzą z języków funkcyjnych. Większość z nich to czyste funkcje. Tworzą nową tablicę, wypełniają ją, stosują określoną funkcję zwrotną do wartości każdego elementu, a następnie zwracają tę nową tablicę.

Wszystkie metody iteracji po tablicy mają podobną składnię. Oryginalna tablica array, wywołanie metody method i funkcja wywołania zwrotnego callback jako argument metody.

            array.method(callback[currentValue, index, array])
          

W większości metod argumentami funkcji zwrotnej są:

  • wartość elementu currentValue (pierwszy parametr),
  • pozycja elementu index (drugi parametr)
  • oraz oryginalna tablica array (trzeci parametr).
            array.method((item, idx, arr) => {
                // logika, która będzie stosowana przy każdej iteracji
            });
          

Wszystkie parametry, z wyjątkiem wartości elementu item, są opcjonalne. Nazwy parametrów mogą być dowolne, ale istnieją nieoficjalne konwencje.

            array.method(item => {
                // logika, która będzie stosowana przy każdej iteracji
            });
          

Section3 Article1: Metoda map()

Metoda map(callback) jest używana do otrzymania przekształconej kopii tablicy. Wywołuje ona funkcję zwrotną dla każdego elementu oryginalnej tablicy i zapisuje jej wynik do nowej tablicy, która będzie wynikiem wykonania metody map.

            tablica.map((element, index, array) => {
                // Ciało funkcji zwrotnej
            });
          
  • Metoda iteruje po oryginalnej tablicy element po elemencie.
  • Nie zmienia oryginalnej tablicy.
  • Wynik funkcji zwrotnej jest zapisywany w nowej tablicy.
  • Zwraca nową tablicę o tej samej długości.

Może być używana do zmiany każdego elementu tablicy. Oryginalna tablica służy jako wzór, na podstawie którego można utworzyć kolejną kolekcję.

            const planets = ["Ziemia", "Mars", "Wenus", "Jowisz"];

            const planetsInUpperCase = planets.map(planet => planet.toUpperCase());
            console.log(planetsInUpperCase);// ['ZIEMIA', 'MARS', 'WENUS', 'JOWISZ']

            const planetsInLowerCase = planets.map(planet => planet.toLowerCase());
            console.log(planetsInLowerCase);// ['ziemia', 'mars', 'wenus', 'jowisz']

            // Oryginalna tablica się nie zmieniła
            console.log(planets);// ['Ziemia', 'Mars', 'Wenus', 'Jowisz']
          

Używanie anonimowych funkcji strzałkowych z niejawnym zwrotem znacznie zmniejsza "szum" deklaracji funkcji wywołania zwrotnego, dzięki czemu kod jest czystszy i łatwiejszy do odczytania.

Section3 Article2: Tablica obiektów

Wiemy już, że jednym z codziennych zadań programisty jest manipulowanie tablicą obiektów. Możemy na przykład chcieć uzyskać tablicę wartości właściwości ze wszystkich obiektów. Przyjmijmy, że istnieje tablica studentów, ale musisz uzyskać osobną tablicę ich imion.

            const students = [
                { name: "Mango", score: 83 },
                { name: "Poly", score: 59 },
                { name: "Ajax", score: 37 },
                { name: "Kiwi", score: 94 },
                { name: "Houston", score: 64 },
            ];

            const names = students.map(student => student.name);
            console.log(names);// ['Mango', 'Poly', 'Ajax', 'Kiwi', 'Houston']
          

Używając metody map(), możesz iterować tablicę obiektów i zwracać wartość właściwości każdego z nich w funkcji zwrotnej.

Section4 Article1: Metoda flatMap()

Metoda flatMap(callback) jest podobna do metody map(), ale jest używana w przypadkach, gdy wynikiem jest tablica wielowymiarowa, która wymaga spłaszczenia.

            tablica.flatMap((element, index, array) => {
                // Ciało funkcji wywołania zwrotnego
            });
          

Tablica students zawiera listę studentów wraz z listą przedmiotów, na które student uczęszcza we właściwości courses. Kilku studentów może uczęszczać na ten sam przedmiot. Konieczne jest sporządzenie listy wszystkich przedmiotów, na które uczęszcza ta grupa studentów, na razie nie przeszkadza nam powtarzanie się przedmiotów.

            const students = [
            { name: "Mango", courses: ["matematyka", "fizyka"] },
            { name: "Poly", courses: ["informatyka", "matematyka"] },
            { name: "Kiwi", courses: ["fizyka", "biologia"] },
            ];

            students.map(student => student.courses);
            // [['matematyka', 'fizyka'], ['informatyka', 'matematyka'], ['fizyka', 'biologia']]

            students.flatMap(student => student.courses);
            // ['matematyka', 'fizyka', 'informatyka', 'matematyka', 'fizyka', 'biologia'];
          

Metoda ta wywołuje funkcję zwrotną dla każdego elementu oryginalnej tablicy i zapisuje wynik swojej pracy w nowej tablicy. Różnica w stosunku do map() polega na tym, że nowa tablica zostanie "spłaszczona" o jeden poziom (jedno zagnieżdżenie). Ta spłaszczona tablica będzie wynikiem flatMap().

Section5 Article1: Metoda filter()

Metoda filter(callback) służy do filtrowania tablicy, gdy konieczne jest wybranie więcej niż jednego elementu ze zbioru według jakiegoś kryterium.

            tablica.filter((element, index, array) => {
                // Ciało funkcji wywołania zwrotnego
            });
          
  • Nie zmienia oryginalnej tablicy.
  • Element po elemencie iteruje po oryginalnej tablicy.
  • Zwraca nową tablicę.
  • Dodaje do zwróconej tablicy elementy, które spełniają warunek funkcji wywołania zwrotnego.
  • Jeśli wywołanie zwrotne zwróciło true, element jest dodawany do zwróconej tablicy.
  • Jeśli wywołanie zwrotne zwróciło false, element nie jest dodawany do zwróconej tablicy.
  • Jeśli żaden z elementów nie spełnia warunku, zwraca pustą tablicę.
            const values = [51, -3, 27, 21, -68, 42, -37];

            const positiveValues = values.filter(value => value >= 0);
            console.log(positiveValues);// [51, 27, 21, 42]

            const negativeValues = values.filter(value => value < 0);
            console.log(negativeValues);// [-3, -68, -37]

            const bigValues = values.filter(value => value > 1000);
            console.log(bigValues);// []

            // Oryginalna tablica się nie zmieniła
            console.log(values);// [51, -3, 27, 21, -68, 42, -37]
          

Oznacza to, że metoda filter wywołuje funkcję zwrotną dla każdego elementu oryginalnej tablicy, a jeśli wynikiem jej wykonania jest true, bieżący element jest dodawany do nowej tablicy.

Section5 Article2: Filtrowanie unikalnych elementów

Używając metody filter(), możesz filtrować tablicę tak, aby pozostały w niej tylko unikalne elementy. To podejście działa tylko z tablicą wartości prymitywnych, a nie obiektów.

Wróćmy do grupy studentów i tablicy wszystkich uczęszczanych przedmiotów, które otrzymaliśmy za pomocą metody flatMap().

            const students = [
            { name: "Mango", courses: ["matematyka", "fizyka"] },
            { name: "Poly", courses: ["informatyka", "matematyka"] },
            { name: "Kiwi", courses: ["fizyka", "biologia"] },
            ];

            const allCourses = students.flatMap(student => student.courses);
            // ['matematyka', 'fizyka', 'informatyka', 'matematyka', 'fizyka', 'biologia'];
          

Zmienna allCourses przechowuje tablicę wszystkich odwiedzonych przedmiotów, które mogą się powtarzać. Zadaniem jest stworzenie nowej tablicy zawierającej tylko unikalne przedmioty, czyli bez powtórzeń.

            const uniqueCourses = allCourses.filter(
            (course, index, array) => array.indexOf(course) === index
            );
          

Używając array.indexOf(course), wyszukujemy pierwsze dopasowanie bieżącego elementu course i pobieramy jego indeks w oryginalnej tablicy wszystkich kursów. Parametr index przechowuje indeks bieżącego elementu course podczas iteracji po tablicy przy użyciu metody filter.

Jeśli wynik indexOf() i wartość index są równe - jest to unikalny element, ponieważ jest to pierwszy przypadek napotkania takiej wartości w tablicy i w bieżącej iteracji filtr przetwarza akurat ją.

            # Tablica wszystkich kursów
            ['matematyka', 'fizyka', 'informatyka', 'matematyka', 'fizyka', 'biologia'];
          

Dla elementu 'matematyka' przy indeksie 0:

  • indexOf() zwróci 0, ponieważ szuka pierwszego dopasowania.
  • Wartość parametru index będzie wynosić 0.
  • Indeksy są równe, więc jest to pierwsze wystąpienie elementu.

Dla elementu 'matematyka' przy indeksie 3:

  • indexOf() zwróci 0, ponieważ szuka pierwszego dopasowania.
  • Wartość parametru index będzie wynosić 3.
  • Indeksy nie są równe, więc jest to powtórzenie, a nie unikalny element.

Section5 Article3 : Tablica obiektów

Podczas pracy z tablicą obiektów filtrowanie odbywa się według wartości wybranej/wybranych właściwości. Rezultatem jest nowa tablica przefiltrowanych obiektów.

Dla przykładu istnieje tablica studentów z wynikami testów. Konieczne jest odfiltrowanie najlepszych (wyniki powyżej 80), najgorszych (wyniki poniżej 50) i średnich (wyniki od 50 do 80).

            const LOW_SCORE = 50;
            const HIGH_SCORE = 80;
            const students = [
            { name: "Mango", score: 83 },
            { name: "Poly", score: 59 },
            { name: "Ajax", score: 37 },
            { name: "Kiwi", score: 94 },
            { name: "Houston", score: 64 },
            ];

            const best = students.filter(student => student.score >= HIGH_SCORE);
            console.log(best);// Tablica obiektów o nazwach Mango i Kiwi

            const worst = students.filter(student => student.score < LOW_SCORE);
            console.log(worst);// Tablica z jednym obiektem Ajax

            // W funkcji wywołania zwrotnego wygodnie 
            // będzie zdestrukturyzować właściwości obiektu
            const average = students.filter(
            ({ score }) => score >= LOW_SCORE && score < HIGH_SCORE
            );
            console.log(average);// Tablica obiektów o imionach Poly i Houston
          

Metoda find()

Metoda filter(callback) jest używana do znalezienia wszystkich elementów spełniających warunek, natomiast metoda find(callback) pozwala na znalezienie i zwrócenie pierwszego pasującego elementu, po odnalezieniu którego iteracja po tablicy zatrzymuje się. Oznacza to, że nawet jeśli mamy wiele elementów spełniających dany warunek, otrzymamy tylko pierwszy element spełniający warunek.

            tablica.find((element, index, array) => {
                // Ciało funkcji wywołania zwrotnego
            });
        
  • Nie zmienia oryginalnej tablicy
  • Element po elemencie iteruje po oryginalnej tablicy.
  • Zwraca pierwszy element, który spełnia warunek, to znaczy, gdy wywołanie zwrotne zwraca true.
  • Jeśli żaden element nie pasuje, czyli dla wszystkich elementów wywołanie zwrotne zwróciło false, metoda zwraca undefined.

Metoda find() powinna służyć do jednego zadania - znalezienia elementu na podstawie unikalnej wartości właściwości. Na przykład może to być wyszukiwanie użytkownika na podstawie adresu email, samochodu na podstawie numeru seryjnego, książki na podstawie numeru ISBN itp.

            const colorPickerOptions = [
            { label: "red", color: "#F44336" },
            { label: "green", color: "#4CAF50" },
            { label: "blue", color: "#2196F3" },
            { label: "pink", color: "#E91E63" },
            { label: "indigo", color: "#3F51B5" },
            ];

            colorPickerOptions.find(option => option.label === "blue");// { label: 'blue', color: '#2196F3' }
            colorPickerOptions.find(option => option.label === "pink");// { label: 'pink', color: '#E91E63' }
            colorPickerOptions.find(option => option.label === "white");// undefined
          

Metoda findIndex()

Metoda findIndex(callback) jest nowoczesnym zamiennikiem metody indexOf(). Umożliwia wyszukiwanie według bardziej złożonych warunków niż tylko ścisła równość. Dzięki temu służy zarówno do wyszukiwania w tablicy elementów typu prymitywnego, jak i w tablicy obiektów.

            tablica.findIndex((element, index, array) => {
                // Ciało funkcji wywołania zwrotnego
            });
        
  • Nie zmienia oryginalnej tablicy.
  • Element po elemencie iteruje po oryginalnej tablicy.
  • Zwraca indeks pierwszego elementu, który spełnia warunek, to znaczy, gdy wywołanie zwrotne zwraca true.
  • Jeśli żaden element nie pasuje, to znaczy dla wszystkich elementów wywołanie zwrotne zwróciło false, metoda zwraca -1.
            const colorPickerOptions = [
            { label: "red", color: "#F44336" },
            { label: "green", color: "#4CAF50" },
            { label: "blue", color: "#2196F3" },
            { label: "pink", color: "#E91E63" },
            { label: "indigo", color: "#3F51B5" },
            ];

            colorPickerOptions.findIndex(option => option.label === "blue");// 2
            colorPickerOptions.findIndex(option => option.label === "pink");// 3
            colorPickerOptions.findIndex(option => option.label === "white");// -1
        

Metody every() i some()

Metoda every()

Sprawdza, czy wszystkie elementy tablicy spełniają warunek dostarczony przez funkcję callback. Zwraca true lub false.

            tablica.every((element, index, array) => {
                // Ciało funkcji wywołania zwrotnego
            });
          
  • Nie zmienia oryginalnej tablicy.
  • Element po elemencie iteruje po oryginalnej tablicy.
  • Zwraca true jeśli wszystkie elementy tablicy spełniają warunek.
  • Zwraca false, jeśli przynajmniej jeden element tablicy nie spełnił warunku.
  • Iteracja po tablicy zostaje natychmiast zakończona, jeśli wywołanie zwrotne zwróci false.
  • Jest odpowiednikiem operatora logicznego &&
            // Czy wszystkie elementy są większe lub równe zero? - tak
            [1, 2, 3, 4, 5].every(value => value >= 0);// true

            // Czy wszystkie elementy są większe lub równe zero? - nie
            [1, 2, 3, -10, 4, 5].every(value => value >= 0);// false
          

Metoda some()

Sprawdza, czy przynajmniej jeden element tablicy przeszedł test dostarczony przez funkcję wywołania zwrotnego. Zwraca true lub false.

            tablica.some((element, index, array) => {
                // Ciało funkcji wywołania zwrotnego
            });
          
  • Nie zmienia oryginalnej tablicy.
  • Element po elemencie iteruje po oryginalnej tablicy.
  • Zwraca true, jeśli przynajmniej jeden element tablicy spełnia warunek.
  • Zwraca false, jeśli żaden element tablicy nie spełnia warunku.
  • Iteracja po tablicy zostaje zakończona, jeśli wywołanie zwrotne zwróci true.
  • Jest odpowiednikiem operatora logicznego ||
            // Czy jest przynajmniej jeden element większy lub równy zero? - tak
            [1, 2, 3, 4, 5].some(value => value >= 0);// true

            // Czy jest przynajmniej jeden element większy lub równy zero? - tak
            [-7, -20, 3, -10, -14].some(value => value >= 0);// true

            // Czy jest przynajmniej jeden element mniejszy od zera? - nie
            [1, 2, 3, 4, 5].some(value => value < 0);// false

            // Czy jest przynajmniej jeden element mniejszy od zera? - tak
            [1, 2, 3, -10, 4, 5].some(value => value < 0);// true
          

Tablica obiektów

Podczas pracy z tablicą obiektów sprawdzana jest wartość niektórych ich właściwości. Na przykład, weźmy tablicę obiektów z owocami i dowiedzmy się, czy wszystkie owoce są w magazynie i czy przynajmniej niektóre owoce są w ilości więcej niż 0 sztuk w magazynie.

            const fruits = [
            { name: "apples", amount: 100 },
            { name: "bananas", amount: 0 },
            { name: "grapes", amount: 50 },
            ];

            // every zwróci true tylko wtedy, jeśli będzie więcej niż 0 sztuk wszystkich owoców
            const allAvailable = fruits.every(fruit => fruit.amount > 0);// false

            // some zwróci true tylko wtedy, jeśli będzie więcej niż 0 sztuk przynajmniej jednego owocu
            const anyAvailable = fruits.some(fruits => fruits.amount > 0);// true
          

Metoda reduce()

Metoda reduce(callback, initialValue) służy do sekwencyjnego przetwarzania każdego elementu tablicy z jednoczesnym przechowywaniem wyniku pośredniego w postaci zmiennej-akumulatora. Jest ona nieco trudniejsza do zrozumienia niż inne, ale jej możliwości są tego warte.

            tablica.reduce((previousValue, element, index, array) => {
                // Ciało funkcji wywołania zwrotnego
            }, initialValue);
          
  • Nie zmienia oryginalnej tablicy.
  • Element po elemencie iteruje po oryginalnej tablicy.
  • Według konwencji powinna zwracać najnowszą wartość akumulatora.
  • Wykonuje dowolne operacje, akumulujące tablicę do jednej zmiennej (dowolnego typu).

Najłatwiej wyobrazić sobie, jak to działa, obliczając sumę elementów tablicy.

            const total = [2, 7, 3, 14, 6].reduce((previousValue, number) => {
                return previousValue + number;
            }, 0);

            console.log(total);// 32
          

Pierwszym parametrem funkcji wywołania zwrotnego (previousValue) jest akumulator, czyli wynik pośredni. Wartość zwrócona przez funkcję wywołania zwrotnego w bieżącej iteracji będzie wartością tego parametru w następnej. Na końcu reduce zwróci ostatnią wartość akumulatora.

Drugim parametrem jest zmienna w której znajdziemy kolejne elementy tablicy podczas iteracji.

Przez drugi argument dla reduce() można przekazać opcjonalną wartość początkową akumulatora — parametr initialValue.

            # Najpierw metoda reduce() tworzy wewnętrzną zmienną-akumulator i
            # przypisuje jej wartość parametru initialValue lub pierwszego elementu
            # tablicy do iteracji, jeśli nie określono initialValue.
            previousValue = 0

            # Następnie funkcja wywołania zwrotnego jest wywoływana dla każdego elementu tablicy. Obecna wartość
            # parametru previousValue jest tym, co funkcja zwrotna zwróciła w ostatniej iteracji.
            Iteracja 1 -> previousValue = 0 -> number = 2 -> return 0 + 2 -> return 2
            Iteracja 2 -> previousValue = 2 -> number = 7 -> return 2 + 7 -> return 9
            Iteracja 3 -> previousValue = 9 -> number = 3 -> return 9 + 3 -> return 12
            Iteracja 4 -> previousValue = 12 -> number = 14 -> return 12 + 14 -> return 26
            Iteracja 5 -> previousValue = 26 -> number = 6 -> return 26 + 6 -> return 32

            # Po iteracji po całej tablicy metoda reduce() zwraca wartość akumulatora.
            Wynik - 32
          

Oznacza to, że metoda reduce() jest używana, gdy trzeba wziąć "wiele" i zredukować do "jednego". W codziennych zadaniach często przydaje się ona do pracy z liczbami.

Tablica obiektów

Podczas pracy z tablicą obiektów redukcja jest wykonywana według wartości jakiejś właściwości. Dla przykładu weźmy tablicę studentów z wynikami testów. Chcemy uzyskać średnią ocenę.

            const students = [
            { name: "Mango", score: 83 },
            { name: "Poly", score: 59 },
            { name: "Ajax", score: 37 },
            { name: "Kiwi", score: 94 },
            { name: "Houston", score: 64 },
            ];

            // Nazwa akumulatora może być dowolna, to tylko parametr funkcji
            const totalScore = students.reduce((total, student) => {
                return total + student.score;
            }, 0);

            const averageScore = totalScore / students.length; // 67.4
          

Zaawansowane użycie reduce

Wyobraźmy, że mamy następujące zadanie: z tablicy postów na Twitterze pojedynczego użytkownika musimy obliczyć sumę wszystkich polubień. Możesz iterować za pomocą pętli for lub forEach, ale każde z tych rozwiązań będzie wymagało zbędnego kodu. Użyjmy więc reduce.

            const tweets = [
            { id: "000", likes: 5, tags: ["js", "nodejs"] },
            { id: "001", likes: 2, tags: ["html", "css"] },
            { id: "002", likes: 17, tags: ["html", "js", "nodejs"] },
            { id: "003", likes: 8, tags: ["css", "react"] },
            { id: "004", likes: 0, tags: ["js", "nodejs", "react"] },
            ];

            // Weźmy wszystkie elementy kolekcji i dodajmy wartości właściwości likes
            // do akumulatora, którego początkowa wartość wynosi 0.
            const likes = tweets.reduce((totalLikes, tweet) => totalLikes + tweet.likes, 0);

            console.log(likes);// 32

            // Liczenie polubień przyda nam się więcej niż raz, napiszmy więc funkcję
            // do zliczania polubień z kolekcji
            const countLikes = tweets => {
                return tweets.reduce((totalLikes, tweet) => totalLikes + tweet.likes, 0);
            };

            console.log(countLikes(tweets));// 32
          

Zwróć uwagę na właściwość tags dla każdego posta. Kontynuując temat reduce, wszystkie tagi pojawiające się w postach zbierzemy w tablicę.

            const tweets = [
            { id: "000", likes: 5, tags: ["js", "nodejs"] },
            { id: "001", likes: 2, tags: ["html", "css"] },
            { id: "002", likes: 17, tags: ["html", "js", "nodejs"] },
            { id: "003", likes: 8, tags: ["css", "react"] },
            { id: "004", likes: 0, tags: ["js", "nodejs", "react"] },
            ];

            // Weźmy wszystkie elementy kolekcji i dodajmy wartości właściwości tags
            // do akumulatora, którego początkową wartość podamy jako pustą tablicę [].
            // W każdej iteracji dodajmy wszystkie elementy tablicy tweet.tags do akumulatora i zwróćmy go.
            const tags = tweets.reduce((allTags, tweet) => {
                allTags.push(...tweet.tags);
                return allTags;
            }, []);

            console.log(tags);

            // Zebranie listy tagów nie jest jednorazową operację, więc napiszmy funkcję
            // do zbioru tagów z kolekcji
            const getTags = tweets =>
            tweets.reduce((allTags, tweet) => {
                allTags.push(...tweet.tags);
                return allTags;  
            }, []);

            console.log(getTags(tweets));
            // [ 'js', 'nodejs', 'html', 'css', 'html', 'js', 'nodejs', 'css', 'react', 'js', 'nodejs', 'react' ]
          

Po zebraniu wszystkich tagów z postów dobrze byłoby policzyć ile razy dany tag wystąpił w tablicy. Znowu skorzystamy tu z reduce

            const tweets = [
            { id: "000", likes: 5, tags: ["js", "nodejs"] },
            { id: "001", likes: 2, tags: ["html", "css"] },
            { id: "002", likes: 17, tags: ["html", "js", "nodejs"] },
            { id: "003", likes: 8, tags: ["css", "react"] },
            { id: "004", likes: 0, tags: ["js", "nodejs", "react"] },
            ];

            const getTags = tweets =>
            tweets.reduce((allTags, tweet) => {
                allTags.push(...tweet.tags);
                return allTags;
            }, []);

            const tags = getTags(tweets);

            // Zadeklarujmy nasz callback jako osobną zmienną i podajmy ją metodzie reduce
            // Jest to standardowa praktyka, jeśli funkcja wywołania zwrotnego jest dość długa lub wielokrotnie używana.
            // Jeśli obiekt-akumulator nie posiada własnej właściwości z kluczem tag,
            // utwórzmy go i zapiszmy mu wartość 0.
            // W przeciwnym razie zwiększamy wartość o 1.
            const getTagStats = (acc, tag) => {
                if (!acc.hasOwnProperty(tag)) {
                    acc[tag] = 0;
                }

                acc[tag] += 1;

                return acc;
            };

            // Początkową wartością akumulatora jest pusty obiekt {}
            const countTags = tags => tags.reduce(getTagStats, {});

            const tagCount = countTags(tags);
            console.log(tagCount);
          

Metoda sort()

Metoda sort() sortuje elementy tablicy, ale w przeciwieństwie do innych metod iteracyjnych sortuje oryginalną tablicę.

  • Sortuje i modyfikuje oryginalną tablicę
  • Zwraca zmodyfikowaną tablicę, czyli link do posortowanej oryginalnej tablicy.
  • Domyślnie sortuje w alfabetycznym porządku rosnącym.
  • Sortowanie odbywa się poprzez przetwarzanie wartości do stringa i porównywanie liczb porządkowych z tabeli Unicode.

Taka tablica liczb zostanie posortowana w porządku rosnącym.

            const scores = [61, 19, 74, 35, 92, 56];
            scores.sort();
            console.log(scores);// [19, 35, 56, 61, 74, 92]
          

Ponieważ jednak wartości domyślne są przetwarzane na string, standardowe sortowanie liczb działa w nietypowy sposób. Dlatego w następnym zadaniu przyjrzymy się, jak ustawić swoją kolejność sortowania.

            const scores = [27, 2, 41, 4, 7, 3, 75];
            scores.sort();
            console.log(scores);// [2, 27, 3, 4, 41, 7, 75]
          

Tablica stringów jest posortowana alfabetycznie.

            const students = ["Vika", "Andrey", "Oleg", "Julia", "Boris", "Katya"];
            students.sort();
            console.log(students);
            // [ 'Andrey', 'Boris', 'Julia', 'Katya', 'Oleg', 'Vika' ]
          

W tym przypadku liczba porządkowa wielkich liter jest mniejsza niż liczba wielkich liter.

            const letters = ["b", "B", "a", "A", "c", "C"];
            letters.sort();
            console.log(letters);// ['A', 'B', 'C', 'a', 'b', 'c']
          

Z uwagi na fakt, że oryginalna tablica jest posortowana, naruszana jest zasada czystości funkcji i nie jest wygodne tworzenie kilku kolekcji pochodnych na podstawie oryginalnej, gdy chcemy na przykład utworzyć kolekcję posortowaną w kolejności rosnącej, a drugą w kolejności malejącej. Dlatego przed sortowaniem tworzymy pełną kopię oryginalnej tablicy i dopiero ja sortujemy.

            const scores = [61, 19, 74, 35, 92, 56];
            const ascendingScores = [...scores].sort();

            console.log(scores);// [61, 19, 74, 35, 92, 56]
            console.log(ascendingScores);// [19, 35, 56, 61, 74, 92]
          

Niestandardowa kolejność sortowania liczb

Aby określić niestandardową kolejność sortowania, metodzie sort(compareFunction) trzeba przekazać funkcję wywołania zwrotnego z dwoma parametrami. Jest to funkcja porównania, kolejność sortowania zależy od jej wyniku. Metoda sort() wywoła ją dla wszystkich par elementów.

            tablica.sort((a, b) => {
                // Ciało funkcji wywołania zwrotnego
            });
          
  • a - to pierwszy element do porównania.
  • b - to drugi element do porównania.

Jeśli wywołanie compareFunction(a, b) zwróci jakąkolwiek wartość ujemną, czyli a jest mniejsze niż b, sortowanie umieści a przed b. To jest sortowanie w porządku rosnącym (ascending).

            const scores = [61, 19, 74, 35, 92, 56];
            const ascendingScores = [...scores].sort((a, b) => a - b);
            console.log(ascendingScores);// [19, 35, 56, 61, 74, 92]
          

Jeśli wywołanie compareFunction(a, b) zwróci dowolną wartość dodatnią większą od zera, czyli b jest większe niż a, sortowanie umieści b przed a. To jest sortowanie malejąco (descending).

            const scores = [61, 19, 74, 35, 92, 56];
            const descendingScores = [...scores].sort((a, b) => b - a);
            console.log(descendingScores);// [92, 74, 61, 56, 35, 19]
          

Jeśli wywołanie compareFunction(a, b) zwróci 0, sortowanie pozostawi a i b niezmienione względem siebie, ale posortuje je względem wszystkich innych elementów. W takim wypadku tak naprawdę nie ma więc znaczenia co zwrócimy, ale warto trzymać się zasad i zwrócić 0

Niestandardowa kolejność sortowania stringów

Aby posortować zmienne tekstowe (stringi) alfabetycznie, rosnąco lub malejąco, użyj metody localeCompare().

            firstString.localeCompare(secondString)
          

Jest ona wywoływana na stringu, który trzeba porównać do (firstString) tego, który jest przekazany jej jako argument (secondString).

            "a".localeCompare("b");// -1
            "b".localeCompare("a");// 1
            "a".localeCompare("a");// 0
            "b".localeCompare("b");// 0
          
  • Zwraca wartość ujemną, jeśli firstString musi znajdować się w kolejności przed secondString
  • Zwraca wartość dodatnią większą od zera, jeśli firstString musi znajdować się w kolejności po secondString
  • Jeśli wartości są takie same, zwracane jest zero.

Jest to przydatne podczas sortowania ciągów, ponieważ metoda sort() oczekuje dokładnie takich wartości od funkcji wywołania zwrotnego.

            const students = ["Vika", "Andrey", "Oleg", "Julia", "Boris", "Katya"];

            const inAlphabetOrder = [...students].sort((a, b) => a.localeCompare(b));
            console.log(inAlphabetOrder);
            // [ 'Andrey', 'Boris', 'Julia', 'Katya', 'Oleg', 'Vika' ]

            const inReversedOrder = [...students].sort((a, b) => b.localeCompare(a));
            console.log(inReversedOrder);
            // [ 'Vika', 'Oleg', 'Katya', 'Julia', 'Boris', 'Andrey' ]
          

Sortowanie obiektów

Podczas pracy z tablicą obiektów sortowanie odbywa się według wartości liczbowej lub stringowej jakiejś właściwości. Na przykład weźmy grupę studentów z wynikami testów. Tablicę obiektów należy posortować rosnąco i malejąco według liczby punktów oraz według imienia ucznia.

            const students = [
            { name: "Mango", score: 83 },
            { name: "Poly", score: 59 },
            { name: "Ajax", score: 37 },
            { name: "Kiwi", score: 94 },
            ];

            const inAscendingScoreOrder = students.sort(
            (firstStudent, secondStudent) => firstStudent.score - secondStudent.score
            );

            const inDescendingScoreOrder = students.sort(
            (firstStudent, secondStudent) => secondStudent.score - firstStudent.score
            );

            const inAlphabeticalOrder = students.sort((firstStudent, secondStudent) =>
            firstStudent.name.localeCompare(secondStudent.name)
            );
          

Łańcuchowanie metod

Weźmy tablicę obiektów z imionami, wynikami i przedmiotami, na które uczęszcza każdy student.

            const students = [
            { name: "Mango", score: 83, courses: ["matematyka", "fizyka"] },
            { name: "Poly", score: 59, courses: ["informatyka", "matematyka"] },
            { name: "Ajax", score: 37, courses: ["fizyka", "biologia"] },
            { name: "Kiwi", score: 94, courses: ["literatura", "informatyka"] },
            ];
          

Musisz posortować tablicę ich imion w porządku rosnącym według wyników testów. W tym celu posortujemy kopię tablicy metodą sort(), a następnie za pomocą metody map() utworzymy tablicę wartości właściwości name z posortowanej tablicy.

            const sortedByAscendingScore = [...students].sort((a, b) => a.score - b.score);
            const names = sortedByAscendingScore.map(student => student.name);
            console.log(names);// ['Ajax', 'Poly', 'Mango', 'Kiwi']
          

Problem polega na tym, że musimy tworzyć zmienne pośrednie po każdej operacji oprócz ostatniej. Zmienna sortedByAscendingScore jest tak naprawdę zbędna.

Możesz pozbyć się takich "martwych" zmiennych, grupując wywołania metod w łańcuchy. Każda następna metoda zostanie wykonana na wyniku poprzedniej.

            const names = [...students]
            .sort((a, b) => a.score - b.score)
            .map(student => student.name);

            console.log(names);// ['Ajax', 'Poly', 'Mango', 'Kiwi']
          
  1. Zrób kopię oryginalnej tablicy przed sortowaniem.
  2. Wywołaj metodę sort() na kopii oryginalnej tablicy.
  3. Zastosuj metodę map() do wyniku metody sort().
  4. Zmiennej names jest przypisywany wynik metody map().

Uzyskajmy posortowaną alfabetycznie tablicę unikalnych odwiedzonych przedmiotów.

            const uniqueSortedCourses = students
            .flatMap(student => student.courses)
            .filter((course, index, array) => array.indexOf(course) === index)
            .sort((a, b) => a.localeCompare(b));

            console.log(uniqueSortedCourses);// ['biologia', 'informatyka', 'literatura', 'matematyka', 'fizyka']
          
  1. Na oryginalnej tablicy wywołaj flatMap() i utwórz spłaszczoną tablicę wszystkich kursów.
  2. Na wyniku metody flatMap() zastosuj metodę filter(), aby odfiltrować unikalne elementy.
  3. Na wyniku metody filter() wywołaj sort().
  4. Zmiennej uniqueSortedCourses przypisywany jest wynik metody sort().

Łańcuch metod może mieć dowolną długość, ale zwykle nie potrzebujemy więcej niż 2-3 operacje. Po pierwsze, metody iteracyjne są używane do stosunkowo prostych operacji z kolekcją. Po drugie, wywołanie każdej kolejnej metody jest dodatkową iteracją po tablicy, co przy większej liczbie elementów może wpłynąć na wydajność.