Podstawy Programowania Funkcyjnego #4 reduce


W tej serii uczymy się Podstaw Programowania Funkcyjnego. Wykorzystamy przy okazji wiedzę zdobytą w serii dotyczącej ES6. Poznaliśmy już funkcje filter
i map
, tym razem zajmiemy się tworzeniem jednej wartości z całej tablicy. Zaczynajmy!
# Czym jest Reduce?
W poprzednich postach poznaliśmy map
i filter
, czyli sposoby na przeszukiwanie i transformowanie tablic, tym razem jednak skorzystamy z bardziej rozbudowanego narzędzia, jakim jest reduce
. Zwrócimy dzięki tej funkcji pojedynczą wartość na podstawie elementów tablicy. W prosty sposób będziemy mogli dodać wszystkie elementy tablicy, albo połączyć w string.
Reduce powinno przyjmować trzy argumenty: funkcję do wykonania na każdym elemencie, wartość początkową i tablicę na której będziemy pracować. Na początek zaimplementujemy sobie taką funkcję, gdzie callback
to funkcja, start
- wartość początkowa, a array
to nasza tablica:
var reduce = function(callback, start, array) {
var value = start;
for (var i = 0; i < array.length; i = i + 1) {
value = callback(value, array[i]);
}
return value;
};
Nasza funkcja przypisuje wartość początkową do zmiennej value
, a następnie iteruje po kolejnych elementach tablicy wykonując dla każdego z nich podaną funkcję. Na końcu jako wynik zostaje zwrócona wartość value
.
const add = (value, element) => value+element;
const concat = (value, element) => `${value} ${element}`;
Stworzyliśmy sobie dwie funkcje, z czego pierwsza z nich - add
służy do dodawania dwóch elementów, a druga - concat
słuzy do konkatenacji, czyli połączenia dwóch stringów w jeden. zapis w ES6 `${value} ${element}`
jest równoważny value+' '+element
.
Możemy teraz skorzystać z naszych funkcji, połączyć je i wykonać na tablicach:
const num = [1, 2, 3, 4, 5];
const WhoAmI = ["I", "am", "the", "one", "who", "knocks"];
reduce(add, 0, num); //15
reduce(concat,"",WhoAmI); //"I am the one who knocks"
Jednak w rzeczywistości nie musimy implementować funkcji reduce, ponieważ w JavaScript jest z nami już od ES5:
num.reduce(add); //15
WhoAmI.reduce(concat); //"I am the one who knocks"
# Wartość początkowa i inne argumenty
callback w Reduce może przyjmować cztery argumenty:
previousValue
, czyli wartość zwróconą w ostatnim wywołaniu funkcji callback, lub wartość początkową jeśli taka została podana,currentValue
, czyli obecnie przetwarzany element tablicy,index
, index przetwarzanego elementu,array
, tablicę na której wykonujemy reduce.
Użyjmy poprzedniego przykładu z dodawaniem, jednak ustawmy wartość początkową na 100
:
num.reduce((value, element) => value+element, 100); //115
# Tablica jako wartość zwracana
Poza stringiem czy liczbą, możemy zwrócić także tablice, wystarczy jako wartość początkową podać pustą tablicę, a następnie dodawać kolejne elementy funkcją push
.
num.reduce((value, element) => {
value.push(element*2);
return value;
}, []); //[ 2, 4, 6, 8, 10 ]
Musimy pamiętać, że nasza funkcja wewnętrzna musi zwracać wartość, inaczej funkcja się nie wykona. W powyższym przykładzie korzystamy z tablicy liczb num
, do pustej tablicy dodajemy kolejne elementy pomnożone przez 2. Na końcu otrzymujemy gotową tablicę. Oczywiście moglibyśmy wykonać to również funkcją map
.
# Obiekt jako wartość zwracana
Poza pojedynczymi wartościami i tablicami, możemy także zbudować obiekt za pomocą reduce. W przykładzie pokaże wam, jak policzyć ilość wystąpień elementów w tablicy.
Tablica która znajduje się poniżej, to wystąpienia słów kluczowych w tekście opisującym fabułę serialu. Znajdziemy teraz najczęściej występujące słowa:
BreakingBad = ["chemistry", "crime", "teacher", "cancer", "student", "cancer", "DEA", "wife", "crystal", "wife", "crime"];
BreakingBad.reduce((obj, tag) => {
obj[tag] = (obj[tag] || 0) + 1;
return obj;
}, {}); /* { chemistry: 1,
crime: 2,
teacher: 1,
cancer: 2,
student: 1,
DEA: 1,
wife: 2,
crystal: 1 } */
Callback powyższego kodu przyjmuje dwa argumenty - zmienną obj
, której wartością początkową jest pusty obiekt, oraz aktualny element tablicy tag
. Element, który aktualnie przetwarzamy staje się kluczem w tablicy i jeśli już taki istnieje to przypisana mu zostaje jego wartość powiększona o jeden. W przypadku jeśli dla danego elementu nie ma jeszcze miejsca w tablicy to zostaje stworzony z ustawioną wartością 0, powiększoną o jeden. Na końcu jak zwykle musimy pamiętać o zwróceniu zmiennej obj
.
Jeśli nadal masz problem ze zrozumieniem kodu, spójrz na poniższą, bardziej rozpisaną wersję:
BreakingBad.reduce((obj, tag) => {
if (!obj[tag]) {
obj[tag] = 1;
} else {
obj[tag] = obj[tag] + 1;
}
return obj;
}, {});