Ostatnio układałem tutorial dla niektórych studentów i natknąłem się na problem, który jestem pewien, że wszyscy napotkaliśmy w tym czy innym czasie. Miałem tablicę obiektów i w każdym z nich miałem id. Chciałem szybkiego i efektywnego sposobu, aby spojrzeć na wszystkie id i znaleźć max id.
W tym artykule przeprowadzę Cię przez 4 różne rozwiązania.
- Array.forEach
- Array.map
- Array.reduce
- Math.max
Ustawienie projektu
Do uruchomienia tego przykładu używam Node, ale możesz równie dobrze użyć JavaScript i wrzucić to na stronę internetową. Pierwszą rzeczą, którą zamierzam zrobić jest wymaganie modułu assert z Node, który daje nam możliwość dostarczenia prostego zestawu testów asercji.
const assert = require("assert");
Następnie utworzysz tablicę znaków. Niedawno przeczytałem książkę Bad Blood, więc postanowiłem użyć niektórych postaci z tej książki. Side note: Jeśli jeszcze nie czytałeś tej książki, gorąco ją polecam. Każda postać w tablicy będzie obiektem, który zawiera id, imię i nazwisko.
const characters = ;
W każdym możliwym rozwiązaniu chcesz napisać logikę, która zwróci największe id w tablicy.
Array.forEach
Twoją pierwszą myślą może być iteracja po tablicy przy użyciu pętli i dlaczego nie, tego nauczono cię robić odkąd zacząłeś pisać kod. Możesz zacząć od zadeklarowania wartości max równej zero, iterować po każdym znaku i jeśli jego id jest większe niż max, zaktualizować max.
let max = 0;characters.forEach(character => { if (character.id > max) { max = character.id; }});assert(max === 444);
Moim głównym celem za każdym razem, gdy piszę kod, jest doprowadzenie czegoś do działania, a następnie poprawienie tego później. Jeśli miałbyś uruchomić ten kod, to z pewnością działa, ale po prostu nie wydaje mi się to właściwe. Co jeśli masz tysiąc lub milion obiektów w tablicy? Za każdym razem, gdy zaczynam iterować po tablicy używając jakiegoś rodzaju pętli do wykonania obliczeń, jest to dla mnie ogromna czerwona flaga.
Array.map
Kiedy pojawia się ta czerwona flaga iterowania po tablicy, natychmiast zadaję sobie pytanie, czy jest to coś, co mapa/filtr/redukcja może rozwiązać. Więc jeśli miałbyś zamiar przyjąć takie podejście, możesz zacząć od metody map. Metoda map()
tworzy nową tablicę z wynikami wywołania podanej funkcji na każdym elemencie tablicy. Użyjesz metody map do stworzenia nowej tablicy, która będzie zawierała tylko id, więc nie będziesz już pracował z obiektami. W tym momencie możesz po prostu użyć normalnej metody sortowania na tablicy, złapać ostatni element na liście i to jest twoje maksymalne id.
const ids = characters.map(user => user.id);const sorted = ids.sort((a, b) => a - b);assert(sorted === 444);
Jeśli chciałbyś być naprawdę fantazyjny, możesz zrobić to wszystko w jednej deklaracji.
assert( characters.map(user => user.id).sort((a, b) => a - b) === 444);
Array.reduce
Metoda reduce()
wykonuje funkcję reduktora (którą dostarczasz) na każdym członku tablicy dając w rezultacie pojedynczą wartość wyjściową. Tutaj zamierzasz wywołać redukcję na tablicy znaków. Zdefiniujesz akumulator, który będzie akumulował wartość zwracaną przez callback. Za każdym razem, gdy funkcja wywołania zwrotnego zostanie wywołana, zwróci wartość i przechowa ją w max. Metoda wywołania zwrotnego patrzy na id znaku i jeśli jest większe niż max, zwraca to, jeśli nie, zwraca max. Na koniec musimy ustawić domyślną wartość dla max i to właśnie robi characters.id
.
const maxId = characters.reduce( (max, character) => (character.id > max ? character.id : max), characters.id);assert(maxId === 444);
Jeśli zauważysz, że jest to podobne podejście do użycia metody forEach, która przewija każdy element i porównuje go do max. Różnica polega na tym, że użycie reduce jest znacznie szybsze. Możesz również użyć kombinacji reduce i kolejnego rozwiązania Math.max()
jeśli chcesz.
Operator Spread
Mimo, że lubię podejście map and reduce znacznie lepiej niż iterowanie nad tablicą to nadal nie jest to dla mnie świetne rozwiązanie. Jeśli zagłębisz się w Math.max dowiesz się, że może on przyjmować listę liczb lub tablicę liczb.
To oznacza, że wszystko co musisz zrobić to uzyskać tablicę id i możesz przekazać ją do funkcji max()
. Jeśli zwracałeś uwagę, zrobiłeś to już wcześniej używając mapy. Oznacza to, że możesz użyć funkcji map, aby uzyskać tablicę id, a następnie przekazać ją do funkcji max()
za pomocą operatora spread.
assert(Math.max(...characters.map(user => user.id)) === 444);
Składnia spread pozwala iterowalnemu wyrażeniu, takiemu jak tablica lub łańcuch znaków, być interpretowanym w miejscach, gdzie oczekuje się zero lub więcej argumentów (dla wywołań funkcji) lub elementów (dla literałów tablicowych), lub wyrażenie obiektowe do interpretacji w miejscach, w których oczekuje się zero lub więcej par klucz-wartość (dla literałów obiektu).
To jest dla mnie najczystsze wyglądające rozwiązanie i tak się składa, że naprawdę mi się podoba.
Wyniki wydajności
Nie miałem zamiaru zamieniać tego w artykuł o programowaniu funkcyjnym w JavaScript, ale właśnie tam wylądowaliśmy. Zrobiłem podstawowe pomiary czasu każdej z funkcji (używając console.time()
&console.timeEnd()
) i znalazłem następujące wyniki.
- testiterate: 0.217ms
- test map: 0.191ms
- testreduce: 0.144ms
- testspread: 0.148ms
Wnioski
Pomimo, że użycie reduce dało nam niewielką przewagę wydajnościową, wolę użyć Math.max i operatora spread. Dla mnie wygląda to po prostu czyściej i jest to coś, co lubię pisać. Jeśli otrzymałbyś ten problem, po jakie rozwiązanie sięgnąłbyś? Czy czegoś mi brakuje? Mam nadzieję, że podobał Ci się spacer po tym, jak ja rozwiązałbym ten problem i do następnego razu…
Happy CodingDan