Introduktion til Array.Reduce()

Konceptet med at "reducere" data

Til denne introduktion vil jeg bruge et imaginært musikbibliotek (bestående af numre og afspilningslister) til kontekst af eksempler. De underliggende begreber gælder også for enhver anden form for applikation.

Når man opererer på data, er det ofte nødvendigt at have viden om dataene i en mere abstrakt form end blot de almindelige elementer. I vores musikbibliotek vil du måske gerne vide, hvor mange forskellige titler der findes på en enkelt kunstner, eller hvor mange kunstnere der er i dit bibliotek. Du skal muligvis også kende alle numre, der bruges i en eller flere afspilningslister. Du vil måske også vise, hvor mange timers musik biblioteket (eller en afspilningsliste) indeholder.
Disse er alle typiske use cases for reduktion. (generelt kan alle former for optælling, filtrering, gruppering, kategorisering, samlende, sammenkædning af data løses ved at anvende reducer. Du kan endda misbruge reducere for kortlægningsdata.)

Vær opmærksom på: Sortering af data med reducer fungerer ikke fint og er ikke i nærheden af ​​det fornuftige horisont.

Hvordan reduce virker

En reduceringsfunktion er generelt en funktion, der tager en akkumulator (dette er et objekt eller en værdi, som du kan tænke på som en tilstand, der er resultatet af det tidligere kald til din reduceringsfunktion) og et dataelement som parametre og returnerer en ny værdi/objekt:

(accumulator, element) => resultValue

Hvad gør reduce meget specielt - i forbindelse med funktionel programmering - er, at den har en tilstand. Ikke selve reducerfunktionen (denne er ideelt set statsløs - for at gøre den let at teste og nem at genbruge på tværs af dit projekt), men processen med at reducere selve dataene. Hver accumulator værdi giver dig det mellemliggende resultat af det forrige trin.

Konceptet med at reducere er især anvendt i Array.prototype.reduce() (det er nok også grunden til navnet). En anden velkendt brug af reducere er i Redux statens ledelsesbibliotek. Den bruger reduceringsfunktioner for at ændre tilstanden på en reproducerbar måde ved hjælp af en handling og den nuværende tilstand. Den aktuelle tilstand overføres som accumulator og handlingen er element parameter, der sendes til reduktionen.

Tilbage til array.reduce()

Array.prototype.reduce() (eller array.reduce() af hver Javascript-forekomst af Array i JS) itererer over hvert element i dets forekomstarray, startende i venstre side (array[0] ) af arrayet, kalder din reducer for hvert element og resultatet af reducer for det forrige element.

const array = [ 1, 2, 3, 5 ];
const finalResult = array.reduce((accumulator, element) => {
    console.log("accumulator: ", accumulator);
    console.log("element: ", element);
    const result = accumulator + element;
    console.log("result: ", result);
    return result;
});
console.log("final result: ", finalResult);

// Console output:
//
// > accumulator: 1
// > element: 2
// > intermediate result: 3
// > accumulator: 3
// > element: 3
// > intermediate result: 6
// > accumulator: 6
// > element: 5
// > intermediate result: 11
// > final result: 11
//

Eksemplet ovenfor ser ud til at være helt fint. Med et nærmere kig kan vi finde et grundlæggende problem, der gør det ikke muligt at skrive mere komplekse reduktionsmidler:
Den første værdi, der sendes til element parameteren for vores reducer er faktisk den anden værdi i arrayet, mens den faktiske første arrayværdi overføres til som accumulator værdi ved første påkaldelse.
Hvorfor er dette et problem?
For mere komplekse reducerere ville det betyde, at vi er nødt til at skelne mellem den første kald og efterfølgende kald, da den første akkumulatorværdi kan være af en helt anden type end array-elementerne er (førende til potentiel undefined is not a function undtagelser under udførelsen, når de ikke håndteres omhyggeligt.
Før du nu øjeblikkeligt lukker denne artikel og begynder at skrive reducering, der kan håndtere data af typen arrayelementer OG akkumulatortype:

Array.prototype.reduce() giver en måde at undgå dette problem helt på:

Det giver dig mulighed for at angive en anden parameter ved siden af ​​din reduktionsfunktion, der skal bruges som den første accumulator værdi. Indstilling af denne parameter vil helt undgå dette problem:

const array = [ 1, 2, 3, 5 ];
const finalResult = array.reduce((accumulator, element) => {
    console.log("accumulator: ", accumulator);
    console.log("element: ", element);
    const result = accumulator + element;
    console.log("result: ", result);
    return result;
}, 0);
console.log("final result: ", finalResult);

// Console output:
//
// > accumulator: 0
// > element: 1
// > intermediate result: 1
// > accumulator: 1
// > element: 2
// > intermediate result: 3
// > accumulator: 3
// > element: 3
// > intermediate result: 6
// > accumulator: 6
// > element: 5
// > intermediate result: 11
// > final result: 11
//

Og det giver os også mulighed for at sende en anden akkumulatortype (men med samme grænseflade) for at ændre den måde, data reduceres fuldstændigt på:

const array = [ 1, 2, 3, 5 ];
const finalResult = array.reduce((accumulator, element) => {
    console.log("accumulator: ", accumulator);
    console.log("element: ", element);
    const result = accumulator + element;
    console.log("result: ", result);
    return result;
}, "ConcatedElements: ");
console.log("final result: ", finalResult);

// Console output:
//
// > accumulator: ConcatedElements: 
// > element: 1
// > intermediate result: ConcatedElements: 1
// > accumulator: ConcatedElements: 1
// > element: 2
// > intermediate result: ConcatedElements: 12
// > accumulator: ConcatedElements: 12
// > element: 3
// > intermediate result: ConcatedElements: 123
// > accumulator: ConcatedElements: 123
// > element: 5
// > intermediate result: ConcatedElements: 1235
// > final result: ConcatedElements: 1235
//

Brug af en streng som første accumulator værdi, sammenkæder elementerne i stedet for at tilføje dem.

Fås i forskellige smagsvarianter

Udover Array.prototype.reduce() der er også Array.prototype.reduceRight() . Dette er grundlæggende det samme, men virker i den modsatte retning:

const array_left = ['1', '2', '3', '4', '5'];
const array_right = ['1', '2', '3', '4', '5'];

const left = array_left.reduce((accumulator, element) => {
    return accumulator + element;
});

const right = array_right.reduceRight((accumulator, element) => {
    return accumulator + element;
});

const equivalentRight = array_left.reverse().reduce((accumulator, element) => {
    return accumulator + element;
});

const equivalentLeft = array_right.reverse().reduceRight((accumulator, element) => {
    return accumulator + element;
});

console.log(left);            
console.log(right);           
console.log(equivalentRight);
console.log(equivalentLeft);

// Console output:
//
// > "12345"
// > "54321"
// > "54321"
// > "12345"
//

Det var det, for denne korte introduktion om array.reduce() . Måske kender du nogle praktiske opskrifter til reducering (f.eks. gruppering af data, forening af data, transformation af et array til et objekt (måske for at bruge det senere som hashmap) eller enhver anden idé, du er velkommen til at skrive det i kommentarerne. Jeg vil inkludere opskrifter (med et link til forfatteren) i min næste artikel om reducering.
Jeg sætter også pris på enhver feedback, kritik eller rettelser.

Jeg håber, at denne artikel hjælper med at sætte mere sjov i Funktionel programmering;-)