Funktionales JavaScript: Fünf Möglichkeiten, um das arithmetische Mittel von Array-Elementen und die .reduce () -Methode zu ermitteln

Array-Iterationsmethoden ähneln dem „Starten von Drogen“ (natürlich sind sie keine Drogen; und ich sage nicht, dass Drogen gut sind; sie sind nur eine Redewendung). Wegen ihnen "setzen" sich viele auf funktionale Programmierung. Die Sache ist, dass sie unglaublich bequem sind. Darüber hinaus sind die meisten dieser Methoden sehr einfach zu verstehen. Methoden wie .map() und .filter() akzeptieren nur ein .filter() und ermöglichen es Ihnen, einfache Probleme zu lösen. Es besteht jedoch das Gefühl, dass die Methode .reduce() vielen Schwierigkeiten bereitet. Es ist etwas schwieriger zu verstehen.



Ich habe bereits darüber geschrieben, warum ich denke, dass .reduce() viele Probleme verursacht. Dies ist teilweise auf die Tatsache zurückzuführen, dass viele Handbücher die Verwendung von .reduce() nur beim Umgang mit Zahlen demonstrieren. Daher habe ich darüber geschrieben, wie viele Aufgaben, die keine arithmetischen Operationen implizieren, mit .reduce() gelöst werden .reduce() . Aber was ist, wenn Sie unbedingt mit Zahlen arbeiten müssen?

Eine typische Verwendung von .reduce() sieht aus wie eine Berechnung des arithmetischen Mittels der Elemente eines Arrays. Auf den ersten Blick scheint diese Aufgabe nichts Besonderes zu sein. Aber sie ist nicht so einfach. Tatsache ist, dass Sie vor der Berechnung des Durchschnitts die folgenden Indikatoren finden müssen:

  1. Die Gesamtanzahl der Array-Elementwerte.
  2. Die Länge des Arrays.

Das alles herauszufinden ist ziemlich einfach. Auch die Berechnung von Durchschnittswerten für numerische Arrays ist keine schwierige Aufgabe. Hier ist ein elementares Beispiel:

 function average(nums) {    return nums.reduce((a, b) => (a + b)) / nums.length; } 

Wie Sie sehen, gibt es hier keine besonderen Unverständnisse. Die Aufgabe wird jedoch schwieriger, wenn Sie mit komplexeren Datenstrukturen arbeiten müssen. Was ist, wenn wir eine Reihe von Objekten haben? Was ist, wenn einige Objekte aus diesem Array gefiltert werden müssen? Was tun, wenn Sie bestimmte numerische Werte aus Objekten extrahieren müssen? In dieser Situation ist die Berechnung des Durchschnittswerts für Array-Elemente bereits etwas komplizierter.

Um dies zu bewältigen, werden wir das Trainingsproblem lösen (es basiert auf dieser Aufgabe mit FreeCodeCamp). Wir werden es auf fünf verschiedene Arten lösen. Jeder von ihnen hat seine eigenen Vor- und Nachteile. Eine Analyse dieser fünf Lösungsansätze zeigt, wie flexibel JavaScript sein kann. Und ich hoffe, dass die Analyse von Lösungen Ihnen .reduce() zum Nachdenken darüber gibt, wie Sie .reduce() in realen Projekten einsetzen können.

Aufgabenübersicht


Angenommen, wir haben eine Reihe von Objekten, die viktorianische Slang-Ausdrücke beschreiben. Sie müssen die Ausdrücke herausfiltern, die nicht in Google Books gefunden werden (die found Eigenschaft der entsprechenden Objekte ist false ), und eine durchschnittliche Bewertung für die Beliebtheit der Ausdrücke ermitteln. So könnten solche Daten aussehen ( von hier übernommen ):

 const victorianSlang = [           term: 'doing the bear',        found: true,        popularity: 108,    },           term: 'katterzem',        found: false,        popularity: null,    },           term: 'bone shaker',        found: true,        popularity: 609,    },           term: 'smothering a parrot',        found: false,        popularity: null,    },           term: 'damfino',        found: true,        popularity: 232,    },           term: 'rain napper',        found: false,        popularity: null,    },           term: 'donkey's breakfast',        found: true,        popularity: 787,    },           term: 'rational costume',        found: true,        popularity: 513,    },           term: 'mind the grease',        found: true,        popularity: 154,    }, ]; 

Betrachten Sie 5 Möglichkeiten, um den Durchschnittswert für die Bewertung der Beliebtheit von Ausdrücken aus diesem Array zu ermitteln.

1. Lösen eines Problems ohne Verwendung von .reduce () (Imperativschleife)


In unserem ersten Ansatz zur Lösung des Problems wird die Methode .reduce() nicht verwendet. Wenn Sie noch nie auf Methoden zum Iterieren von Arrays gestoßen sind, hoffe ich, dass das Parsen dieses Beispiels die Situation für Sie ein wenig klarer macht.

 let popularitySum = 0; let itemsFound = 0; const len = victorianSlang.length; let item = null; for (let i = 0; i < len; i++) {    item = victorianSlang[i];    if (item.found) {        popularitySum = item.popularity + popularitySum;        itemsFound = itemsFound + 1;   } const averagePopularity = popularitySum / itemsFound; console.log("Average popularity:", averagePopularity); 

Wenn Sie mit JavaScript vertraut sind, werden Sie dieses Beispiel leicht verstehen. Tatsächlich passiert hier Folgendes:

  1. Wir initialisieren die Variablen itemsFound und itemsFound . Die erste Variable, popularitySum , speichert die Gesamtbewertung der Beliebtheit von Ausdrücken. Und die zweite Variable, itemsFound (das ist eine Überraschung), speichert die Anzahl der gefundenen Ausdrücke.
  2. Dann initialisieren wir die Konstante len und das variable item , die für uns beim Durchlaufen des Arrays nützlich sind.
  3. In einer for Schleife wird der Zähler i inkrementiert, bis sein Wert den Indexwert des letzten Elements des Arrays erreicht.
  4. Innerhalb der Schleife nehmen wir das Element des Arrays, das wir untersuchen möchten. Wir greifen auf das Element mit dem Konstruktor victorianSlang[i] .
  5. Dann finden wir heraus, ob dieser Ausdruck in der Büchersammlung gefunden wird.
  6. Wenn ein Ausdruck in Büchern vorkommt, nehmen wir den Wert seiner Beliebtheitsbewertung und addieren ihn zum Wert der Variablen popularitySum .
  7. Gleichzeitig erhöhen wir auch den Zähler der gefundenen Ausdrücke - itemsFound .
  8. Und schließlich finden wir den Durchschnitt, indem wir popularitySum durch itemsFound .

Also haben wir die Aufgabe gemeistert. Vielleicht war unsere Entscheidung nicht besonders schön, aber sie macht ihren Job. Die Verwendung von Methoden zum Durchlaufen von Arrays macht es ein wenig sauberer. Werfen wir einen Blick darauf, ob es uns gelingt, diese Entscheidung zu „bereinigen“.

2. Einfache Lösung Nr. 1: .filter (), .map () und Ermitteln der Menge mit .reduce ()


Lassen Sie uns vor dem ersten Versuch, das Problem mit den Methoden von Arrays zu lösen, es in kleine Teile zerlegen. Folgendes müssen wir nämlich tun:

  1. Wählen Sie Objekte aus, die Ausdrücke in der Google Books-Sammlung darstellen. Hier können Sie die Methode .filter() verwenden.
  2. Extrahieren Sie aus den Objekten die Bewertung der Popularität von Ausdrücken. Um diese Unteraufgabe zu lösen, ist die Methode .map() geeignet.
  3. Berechnen Sie die Summe der Bewertungen. Hier können wir auf die Hilfe unseres alten Freundes .reduce() .
  4. Und schließlich finden Sie den Durchschnittswert der Schätzungen.

So sieht es im Code aus:

 //   // ---------------------------------------------------------------------------- function isFound(item) {    return item.found; }; function getPopularity(item) {    return item.popularity; } function addScores(runningTotal, popularity) {    return runningTotal + popularity; } //  // ---------------------------------------------------------------------------- //  ,      . const foundSlangTerms = victorianSlang.filter(isFound); //   ,   . const popularityScores = foundSlangTerms.map(getPopularity); //     .    ,    //   ,  reduce     ,  0. const scoresTotal = popularityScores.reduce(addScores, 0); //       . const averagePopularity = scoresTotal / popularityScores.length; console.log("Average popularity:", averagePopularity); 

Schauen addScore sich die Funktion addScore und die Zeile an, in der .reduce() aufgerufen wird. Beachten Sie, dass addScore zwei Parameter akzeptiert. Das erste, runningTotal , wird als Batterie bezeichnet. Es speichert die Summe der Werte. Sein Wert ändert sich jedes Mal, wenn wir über das Array iterieren und die return ausführen. Der zweite Parameter, popularity , ist ein separates Element des Arrays, das wir verarbeiten. Zu Beginn der addScore über das Array wurde die addScore return in addScore nie ausgeführt. Dies bedeutet, dass runningTotal noch nicht automatisch festgelegt wurde. Daher .reduce() wir durch Aufrufen von .reduce() an diese Methode den Wert, der zu Beginn in .reduce() geschrieben werden muss. Dies ist der zweite Parameter, der an .reduce() .

Daher haben wir die Methoden zum Iterieren von Arrays angewendet, um das Problem zu lösen. Die neue Version der Lösung erwies sich als viel sauberer als die vorherige. Mit anderen Worten, die Entscheidung erwies sich als aussagekräftiger. Wir teilen JavaScript nicht genau mit, wie die Schleife ausgeführt werden soll, sondern folgen nicht den Indizes der Elemente von Arrays. Stattdessen deklarieren wir einfache Hilfsfunktionen von geringer Größe und kombinieren sie. Die ganze harte Arbeit erledigen die Array-Methoden .filter() , .map() und .reduce() für uns. Dieser Ansatz zur Lösung solcher Probleme ist aussagekräftiger. Diese Array-Methoden sind viel vollständiger als die Schleife. Sie geben Auskunft über die im Code festgelegte Absicht.

3. Einfache Lösung Nr. 2: Verwenden mehrerer Batterien


In der vorherigen Version der Lösung haben wir eine ganze Reihe von Zwischenvariablen erstellt. Zum Beispiel foundSlangTerms und popularityScores . In unserem Fall ist eine solche Lösung durchaus akzeptabel. Was aber, wenn wir uns ein komplexeres Ziel für das Code-Design setzen? Es wäre schön, wenn wir das fließende Interface- Design-Muster im Programm verwenden könnten. Mit diesem Ansatz könnten wir die Aufrufe aller Funktionen verketten und auf Zwischenvariablen verzichten. Hier erwartet uns jedoch ein Problem. Beachten Sie, dass wir den Wert von popularityScores.length . Wenn wir alles verketten wollen, brauchen wir eine andere Methode, um die Anzahl der Elemente im Array zu ermitteln. Die Anzahl der Elemente im Array spielt die Rolle eines Divisors bei der Berechnung des Durchschnittswerts. Mal sehen, ob wir den Ansatz zur Lösung des Problems so ändern können, dass alles durch Kombinieren von Methodenaufrufen in einer Kette erledigt werden kann. Wir werden dies tun, indem wir zwei Werte verfolgen, wenn wir über die Elemente des Arrays iterieren, dh mit der „Doppelbatterie“.

 //   // --------------------------------------------------------------------------------- function isFound(item) {    return item.found; }; function getPopularity(item) {    return item.popularity; } //    ,  return,   . function addScores({totalPopularity, itemCount}, popularity) {    return {        totalPopularity: totalPopularity + popularity,        itemCount:    itemCount + 1,    }; } //  // --------------------------------------------------------------------------------- const initialInfo  = {totalPopularity: 0, itemCount: 0}; const popularityInfo = victorianSlang.filter(isFound)    .map(getPopularity)    .reduce(addScores, initialInfo); //       . const {totalPopularity, itemCount} = popularityInfo; const averagePopularity = totalPopularity / itemCount; console.log("Average popularity:", averagePopularity); 

Um mit zwei Werten zu arbeiten, haben wir hier das Objekt in der Reduzierungsfunktion verwendet. Bei jedem Durchlauf durch das mit addScrores durchgeführte Array addScrores wir den Gesamtwert der Beliebtheitsbewertung und die Anzahl der Elemente. Es ist wichtig zu beachten, dass diese beiden Werte als ein einzelnes Objekt dargestellt werden. Mit diesem Ansatz können wir das System „austricksen“ und zwei Entitäten innerhalb desselben Rückgabewerts speichern.

Die Funktion addScrores als etwas komplizierter als die Funktion mit demselben Namen im vorherigen Beispiel. Jetzt stellt sich jedoch heraus, dass wir eine einzelne Kette von Methodenaufrufen verwenden können, um alle Operationen mit dem Array auszuführen. Als Ergebnis der Verarbeitung des Arrays erhalten wir das Objekt popularInfo, in dem alles gespeichert ist, was Sie zum Ermitteln des Durchschnitts benötigen. Dies macht die Anrufkette ordentlich und einfach.

Wenn Sie den Wunsch haben, diesen Code zu verbessern, können Sie damit experimentieren. Zum Beispiel - Sie können es wiederholen, um viele Zwischenvariablen zu entfernen. Sie können sogar versuchen, diesen Code in eine Zeile zu setzen.

4. Zusammensetzung von Funktionen ohne Punktnotation


Wenn Sie mit funktionaler Programmierung noch nicht vertraut sind oder wenn Ihnen die funktionale Programmierung zu kompliziert erscheint, können Sie diesen Abschnitt überspringen. Das Parsen kommt Ihnen zugute, wenn Sie bereits mit curry() und compose() vertraut sind. Wenn Sie sich mit diesem Thema befassen möchten, werfen Sie einen Blick auf dieses Material zur funktionalen Programmierung in JavaScript und insbesondere auf den dritten Teil der Reihe, in der es enthalten ist.

Wir sind Programmierer, die einen funktionalen Ansatz verfolgen. Dies bedeutet, dass wir uns bemühen, komplexe Funktionen aus anderen Funktionen aufzubauen - klein und einfach. Bisher haben wir im Zuge der Prüfung verschiedener Optionen zur Lösung des Problems die Anzahl der Zwischenvariablen reduziert. Infolgedessen wurde der Lösungscode immer einfacher. Aber was ist, wenn diese Idee auf die Spitze getrieben wird? Was ist, wenn Sie versuchen, alle Zwischenvariablen loszuwerden? Und sogar versuchen, von einigen Parametern wegzukommen?

Sie können eine Funktion erstellen, um den Durchschnitt nur mit der Funktion compose() zu berechnen, ohne Variablen zu verwenden. Wir nennen dies "Programmierung ohne feinkörnige Notation" oder "implizite Programmierung". Um solche Programme zu schreiben, benötigen Sie viele Hilfsfunktionen.

Manchmal schockiert ein solcher Code Menschen. Dies liegt an der Tatsache, dass sich ein solcher Ansatz stark von dem allgemein akzeptierten unterscheidet. Ich fand jedoch heraus, dass das Schreiben von Code im Stil der impliziten Programmierung eine der schnellsten Methoden ist, um die Essenz der funktionalen Programmierung zu verstehen. Daher kann ich Ihnen raten, diese Technik in einem persönlichen Projekt auszuprobieren. Aber ich möchte sagen, dass Sie den Code, den andere Leute lesen müssen, vielleicht nicht im Stil der impliziten Programmierung schreiben sollten.

Zurück zu unserer Aufgabe, ein System zur Berechnung von Durchschnittswerten zu konstruieren. Um Platz zu sparen, werden wir hier auf die Verwendung von Pfeilfunktionen eingehen. In der Regel ist es besser, benannte Funktionen zu verwenden. Hier ist ein guter Artikel zu diesem Thema. Auf diese Weise erhalten Sie im Fehlerfall bessere Stack-Trace-Ergebnisse.

 //   // ---------------------------------------------------------------------------- const filter = p => a => a.filter(p); const map   = f => a => a.map(f); const prop  = k => x => x[k]; const reduce = r => i => a => a.reduce(r, i); const compose = (...fns) => (arg) => fns.reduceRight((arg, fn) => fn(arg), arg); //  -   "blackbird combinator". //     : https://jrsinclair.com/articles/2019/compose-js-functions-multiple-parameters/ const B1 = f => g => h => x => f(g(x))(h(x)); //  // ---------------------------------------------------------------------------- //   sum,    . const sum = reduce((a, i) => a + i)(0); //     . const length = a => a.length; //       . const div = a => b => a / b; //   compose()        . //    compose()     . const calcPopularity = compose(    B1(div)(sum)(length),    map(prop('popularity')),    filter(prop('found')), ); const averagePopularity = calcPopularity(victorianSlang); console.log("Average popularity:", averagePopularity); 

Wenn Ihnen all dieser Code völlig unsinnig erscheint, machen Sie sich darüber keine Sorgen. Ich habe es hier als intellektuelle Übung aufgenommen, um Sie nicht zu verärgern.

In diesem Fall liegt die Hauptarbeit in der Funktion compose() . Wenn Sie den Inhalt von unten nach oben lesen, stellt sich heraus, dass die Berechnungen damit beginnen, das Array nach der Eigenschaft der found Elemente zu filtern. Dann rufen wir die popularity Eigenschaft mit map() . Danach verwenden wir den sogenannten „ Amselkombinator “. Diese Entität wird als Funktion B1 , mit der zwei Berechnungsdurchläufe für einen Satz von Eingabedaten durchgeführt werden. Schauen Sie sich zum besseren Verständnis die folgenden Beispiele an:

 //   ,  , : const avg1 = B1(div)(sum)(length); const avg2 = arr => div(sum(arr))(length(arr)); const avg3 = arr => ( sum(arr) / length(arr) ); const avg4 = arr => arr.reduce((a, x) => a + x, 0) / arr.length; 

Auch wenn Sie nichts mehr verstehen - machen Sie sich keine Sorgen. Dies ist nur eine Demonstration, dass JavaScript auf sehr unterschiedliche Weise geschrieben werden kann. Von diesen Merkmalen ist dies die Schönheit dieser Sprache.

5. Lösen Sie das Problem in einem Durchgang mit der Berechnung des kumulierten Durchschnittswerts


Alle oben genannten Software-Konstrukte lösen unser Problem (einschließlich des Imperativzyklus) gut. Diejenigen, die die .reduce() -Methode verwenden, haben etwas gemeinsam. Sie basieren darauf, das Problem in kleine Fragmente zu zerlegen. Diese Fragmente werden dann auf verschiedene Arten zusammengesetzt. Wenn Sie diese Lösungen analysieren, stellen Sie möglicherweise fest, dass wir das Array dreimal durchlaufen. Es besteht das Gefühl, dass es unwirksam ist. Es wäre schön, wenn es eine Möglichkeit gäbe, das Array zu verarbeiten und das Ergebnis in einem Durchgang zurückzugeben. Diese Methode existiert, für ihre Anwendung muss jedoch auf Mathematik zurückgegriffen werden.

Um den Durchschnittswert für Array-Elemente in einem Durchgang zu berechnen, benötigen wir eine neue Methode. Sie müssen einen Weg finden, um den Durchschnitt anhand des zuvor berechneten Durchschnitts und des neuen Werts zu berechnen. Wir suchen diese Methode mit Algebra.

Der Durchschnittswert von n Zahlen kann mit dieser Formel ermittelt werden:


Um die durchschnittlichen n + 1 Zahlen herauszufinden, reicht dieselbe Formel aus, jedoch in einem anderen Eintrag:


Diese Formel ist dieselbe wie folgt:


Und das Gleiche wie das:


Wenn Sie dies ein wenig konvertieren, erhalten Sie Folgendes:


Wenn Sie den Punkt in all dem nicht sehen, ist es in Ordnung. Das Ergebnis all dieser Transformationen ist, dass wir mit Hilfe der letzten Formel den Durchschnittswert während eines einzelnen Durchlaufs des Arrays berechnen können. Dazu müssen Sie den Wert des aktuellen Elements, den im vorherigen Schritt berechneten Durchschnittswert und die Anzahl der Elemente kennen. Darüber hinaus können die meisten Berechnungen in der Reduzierfunktion durchgeführt werden:

 //      // ---------------------------------------------------------------------------- function averageScores({avg, n}, slangTermInfo) {    if (!slangTermInfo.found) {        return {avg, n};       return {        avg: (slangTermInfo.popularity + n * avg) / (n + 1),        n:  n + 1,    }; } //  // ---------------------------------------------------------------------------- //       . const initialVals    = {avg: 0, n: 0}; const averagePopularity = victorianSlang.reduce(averageScores, initialVals).avg; console.log("Average popularity:", averagePopularity); 

Dank dieses Ansatzes kann der erforderliche Wert nur einmal unter Umgehung des Arrays gefunden werden. Andere Ansätze verwenden einen Durchgang, um das Array zu filtern, einen anderen, um die erforderlichen Daten daraus zu extrahieren, und einen anderen, um die Summe der Werte der Elemente zu ermitteln. Hier passt alles in einen Durchgang durch das Array.

Bitte beachten Sie, dass dies die Berechnungen nicht unbedingt effizienter macht. Bei diesem Ansatz müssen mehr Berechnungen durchgeführt werden. Wenn jeder neue Wert eintrifft, führen wir die Multiplikations- und Divisionsoperationen durch, um den aktuellen Durchschnittswert im aktuellen Zustand zu halten. Bei anderen Lösungen für dieses Problem teilen wir eine Zahl nur einmal in eine andere - am Ende des Programms. Dieser Ansatz ist jedoch in Bezug auf die Speichernutzung viel effizienter. Zwischenarrays werden hier nicht verwendet, daher müssen wir nur ein Objekt mit zwei Werten im Speicher speichern.

. . , . . , , .

?


? , . , - . , , , . , . , . , , .

, - , . , ? . . — .


:

  1. .reduce() .
  2. .filter() .map() , — .reduce() .
  3. , .
  4. .
  5. .

, -, ? — . - — , :

  1. , . — .
  2. , , — .
  3. , , — , .

! JavaScript-?

Source: https://habr.com/ru/post/de458030/


All Articles