JavaScript fonctionnel: cinq façons de trouver la moyenne arithmétique des éléments d'un tableau et la méthode .reduce ()

Les méthodes d'itération des tableaux sont similaires aux «médicaments de départ» (bien sûr, ce ne sont pas des médicaments; et je ne dis pas que les médicaments sont bons; ils ne sont qu'une figure de style). À cause d'eux, beaucoup de «s'asseoir» sur la programmation fonctionnelle. Le fait est qu'ils sont incroyablement pratiques. De plus, la plupart de ces méthodes sont très faciles à comprendre. Des méthodes comme .map() et .filter() n'acceptent qu'un seul argument de rappel et vous permettent de résoudre des problèmes simples. Mais il y a un sentiment que la méthode .reduce() cause des difficultés à beaucoup. Le comprendre est un peu plus difficile.



J'ai déjà .reduce() pourquoi je pense que .reduce() crée beaucoup de problèmes. Cela est dû en partie au fait que de nombreux manuels démontrent l'utilisation de .reduce() uniquement lors de la gestion des nombres. Par conséquent, j'ai écrit combien de tâches qui n'impliquent pas d'opérations arithmétiques peuvent être résolues en utilisant .reduce() . Mais que faire si vous avez absolument besoin de travailler avec des chiffres?

Une utilisation typique de .reduce() ressemble à un calcul de la moyenne arithmétique des éléments d'un tableau. À première vue, il semble qu'il n'y ait rien de spécial dans cette tâche. Mais elle n'est pas si simple. Le fait est qu'avant de calculer la moyenne, vous devez trouver les indicateurs suivants:

  1. Le montant total des valeurs des éléments du tableau.
  2. La longueur du tableau.

Découvrir tout cela est assez simple. Et le calcul des valeurs moyennes pour les tableaux numériques n'est pas non plus une opération difficile. Voici un exemple élémentaire:

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

Comme vous pouvez le voir, il n'y a pas d'incompréhensions particulières ici. Mais la tâche devient plus difficile si vous devez travailler avec des structures de données plus complexes. Et si nous avons un tableau d'objets? Que faire si certains objets de ce tableau doivent être filtrés? Que faire si vous devez extraire certaines valeurs numériques d'objets? Dans cette situation, le calcul de la valeur moyenne des éléments du tableau est déjà une tâche un peu plus compliquée.

Pour résoudre ce problème, nous allons résoudre le problème de formation (il est basé sur cette tâche avec FreeCodeCamp). Nous allons le résoudre de cinq manières différentes. Chacun d'eux a ses propres avantages et inconvénients. Une analyse de ces cinq approches pour résoudre ce problème montrera à quel point JavaScript peut être flexible. Et j'espère que l'analyse des solutions vous donnera matière à réflexion sur la manière d'utiliser .reduce() dans des projets réels.

Aperçu des tâches


Supposons que nous ayons un tableau d'objets qui décrivent des expressions d'argot victorien. Vous devez filtrer les expressions qui ne se trouvent pas dans Google Livres (la propriété found des objets correspondants est false ) et trouver une note moyenne pour la popularité des expressions. Voici à quoi pourraient ressembler ces données (prises à partir d'ici ):

 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,    }, ]; 

Considérez 5 façons de trouver la valeur moyenne de l'évaluation de la popularité des expressions de ce tableau.

1. Résoudre un problème sans utiliser .reduce () (boucle impérative)


Dans notre première approche pour résoudre le problème, la méthode .reduce() ne sera pas utilisée. Si vous n'avez jamais rencontré de méthodes d'itération de tableaux auparavant, j'espère que l'analyse de cet exemple clarifiera un peu la situation pour vous.

 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); 

Si vous connaissez JavaScript, vous comprendrez facilement cet exemple. En fait, ce qui suit se produit ici:

  1. Nous initialisons les variables itemsFound et itemsFound . La première variable, popularitySum , stocke la note globale de popularité des expressions. Et la deuxième variable, itemsFound , (c'est une surprise) stocke le nombre d'expressions trouvées.
  2. Ensuite, nous initialisons la constante len et l' item variable, qui nous sont utiles lors de la traversée du tableau.
  3. Dans une boucle for , le compteur i incrémenté jusqu'à ce que sa valeur atteigne la valeur d'index du dernier élément du tableau.
  4. À l'intérieur de la boucle, nous prenons l'élément du tableau que nous voulons explorer. Nous accédons à l'élément en utilisant la construction victorianSlang[i] .
  5. Nous découvrons ensuite si cette expression se retrouve dans la collection de livres.
  6. Si une expression apparaît dans les livres, nous prenons la valeur de sa cote de popularité et ajoutons à la valeur de la variable popularitySum .
  7. Dans le même temps, nous itemsFound également le compteur des expressions trouvées - itemsFound .
  8. Et enfin, nous trouvons la moyenne en divisant la popularitySum par les itemsFound .

Nous avons donc fait face à la tâche. Peut-être que notre décision n'était pas particulièrement belle, mais elle fait son travail. L'utilisation de méthodes pour parcourir les tableaux le rendra un peu plus propre. Voyons si nous réussissons, et la vérité est, pour «nettoyer» cette décision.

2. Solution simple n ° 1: .filter (), .map () et recherche du montant à l'aide de .reduce ()


Disons, avant la première tentative d'utiliser les méthodes de tableaux pour résoudre le problème, nous le divisons en petites parties. À savoir, voici ce que nous devons faire:

  1. Sélectionnez des objets représentant des expressions qui se trouvent dans la collection Google Livres. Ici, vous pouvez utiliser la méthode .filter() .
  2. Extraire des objets l'évaluation de la popularité des expressions. Pour résoudre cette sous-tâche, la méthode .map() convient.
  3. Calculez la somme des notes. Ici, nous pouvons recourir à l'aide de notre vieil ami .reduce() .
  4. Et enfin, trouvez la valeur moyenne des estimations.

Voici à quoi cela ressemble dans le code:

 //   // ---------------------------------------------------------------------------- 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); 

addScore œil à la fonction addScore et à la ligne où .reduce() appelé. Notez que addScore accepte deux paramètres. Le premier, runningTotal , est connu sous le nom de batterie. Il stocke la somme des valeurs. Sa valeur change à chaque fois que nous parcourons le tableau et exécutons l' return . Le deuxième paramètre, la popularity , est un élément distinct du tableau que nous traitons. Au tout début de l' addScore sur le tableau, l' return addScore dans addScore n'a jamais été exécutée. Cela signifie que runningTotal pas encore été défini automatiquement. Par conséquent, en appelant .reduce() , nous transmettons à cette méthode la valeur qui doit être écrite dans runningTotal au tout début. Il s'agit du deuxième paramètre passé à .reduce() .

Nous avons donc appliqué les méthodes d'itération des tableaux pour résoudre le problème. La nouvelle version de la solution s'est avérée beaucoup plus propre que la précédente. En d'autres termes, la décision s'est avérée plus déclarative. Nous ne disons pas à JavaScript exactement comment exécuter la boucle; nous ne suivons pas les index des éléments du tableau. Au lieu de cela, nous déclarons des fonctions d'assistance simples de petite taille et les combinons. Tout le travail acharné est fait pour nous par les méthodes de tableau .filter() , .map() et .reduce() . Cette approche pour résoudre de tels problèmes est plus expressive. Ces méthodes de tableau sont beaucoup plus complètes que la boucle ne peut le faire, elles nous renseignent sur l'intention énoncée dans le code.

3. Solution facile n ° 2: utilisation de plusieurs batteries


Dans la version précédente de la solution, nous avons créé tout un tas de variables intermédiaires. Par exemple, foundSlangTerms et popularityScores . Dans notre cas, une telle solution est tout à fait acceptable. Mais que se passe-t-il si nous nous fixons un objectif plus complexe concernant la conception de code? Ce serait bien si nous pouvions utiliser le modèle de conception d' interface fluide dans le programme. Avec cette approche, nous serions en mesure de chaîner les appels de toutes les fonctions et de pouvoir nous passer de variables intermédiaires. Cependant, un problème nous attend ici. Notez que nous devons obtenir la valeur de popularityScores.length . Si nous allons tout enchaîner, nous avons besoin d'une autre façon de trouver le nombre d'éléments dans le tableau. Le nombre d'éléments dans le tableau joue le rôle d'un diviseur dans le calcul de la valeur moyenne. Voyons si nous pouvons changer l'approche pour résoudre le problème afin que tout puisse être fait en combinant les appels de méthode dans une chaîne. Nous le ferons en suivant deux valeurs lors de l'itération sur les éléments du tableau, c'est-à-dire en utilisant la «double batterie».

 //   // --------------------------------------------------------------------------------- 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); 

Ici, pour travailler avec deux valeurs, nous avons utilisé l'objet dans la fonction de réduction. À chaque passage dans le tableau effectué à l'aide d' addScrores , nous mettons à jour la valeur totale de la cote de popularité et le nombre d'éléments. Il est important de noter que ces deux valeurs sont représentées comme un seul objet. Avec cette approche, nous pouvons «tromper» le système et stocker deux entités dans la même valeur de retour.

La fonction addScrores être un peu plus compliquée que la fonction du même nom dans l'exemple précédent. Mais maintenant, il s'avère que nous pouvons utiliser une seule chaîne d'appels de méthode pour effectuer toutes les opérations avec le tableau. À la suite du traitement du tableau, nous obtenons l'objet popularInfo, qui stocke tout ce dont vous avez besoin pour trouver la moyenne. Cela rend la chaîne d'appel nette et simple.

Si vous ressentez le désir d'améliorer ce code, vous pouvez l'expérimenter. Par exemple - vous pouvez le refaire afin de vous débarrasser de nombreuses variables intermédiaires. Vous pouvez même essayer de mettre ce code sur une seule ligne.

4. Composition des fonctions sans utiliser la notation par points


Si vous êtes nouveau dans la programmation fonctionnelle, ou s'il vous semble que la programmation fonctionnelle est trop compliquée, vous pouvez ignorer cette section. Son analyse vous sera utile si vous êtes déjà familier avec curry() et compose() . Si vous souhaitez approfondir ce sujet, jetez un œil à ce matériel sur la programmation fonctionnelle en JavaScript et, en particulier, à la troisième partie de la série dans laquelle il est inclus.

Nous sommes des programmeurs qui adoptent une approche fonctionnelle. Cela signifie que nous nous efforçons de créer des fonctions complexes à partir d'autres fonctions - petites et simples. Jusqu'à présent, au cours de l'examen de diverses options pour résoudre le problème, nous avons réduit le nombre de variables intermédiaires. En conséquence, le code de la solution est devenu plus simple et plus facile. Mais que se passe-t-il si cette idée est poussée à l'extrême? Et si vous essayez de vous débarrasser de toutes les variables intermédiaires? Et même essayer de s'éloigner de certains paramètres?

Vous pouvez créer une fonction pour calculer la moyenne en utilisant la fonction compose() seule, sans utiliser de variables. Nous appelons cela «programmation sans utiliser de notation à grain fin» ou «programmation implicite». Pour écrire de tels programmes, vous aurez besoin de nombreuses fonctions auxiliaires.

Parfois, un tel code choque les gens. Cela est dû au fait qu'une telle approche est très différente de celle généralement acceptée. Mais j'ai découvert que l'écriture de code dans le style de la programmation implicite est l'un des moyens les plus rapides pour comprendre l'essence de la programmation fonctionnelle. Par conséquent, je peux vous conseiller d'essayer cette technique dans un projet personnel. Mais je veux dire que vous ne devriez peut-être pas écrire dans le style de programmation implicite le code que les autres doivent lire.

Revenons donc à notre tâche de construire un système de calcul des moyennes. Par souci d'économie d'espace, nous allons passer ici à l'utilisation des fonctions flèches. En règle générale, il est préférable d'utiliser des fonctions nommées. Voici un bon article sur ce sujet. Cela vous permet d'obtenir de meilleurs résultats de trace de pile en cas d'erreurs.

 //   // ---------------------------------------------------------------------------- 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); 

Si tout ce code vous semble complètement insensé, ne vous en faites pas. Je l'ai inclus ici comme un exercice intellectuel, et non pour vous déranger.

Dans ce cas, le travail principal est dans la fonction compose() . Si vous lisez son contenu de bas en haut, il s'avère que les calculs commencent par filtrer le tableau par la propriété de ses éléments found . Ensuite, nous récupérons la propriété de l'élément de popularity aide de map() . Après cela, nous utilisons le soi-disant « combinateur merle ». Cette entité est représentée comme une fonction B1 , qui est utilisée pour effectuer deux passes de calculs sur un ensemble de données d'entrée. Pour mieux comprendre cela, jetez un œil à ces exemples:

 //   ,  , : 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; 

Encore une fois, si vous ne comprenez plus rien - ne vous inquiétez pas. Ceci est juste une démonstration que JavaScript peut être écrit de différentes manières. De ces caractéristiques, c'est la beauté de ce langage.

5. Résoudre le problème en un seul passage avec le calcul de la valeur moyenne cumulée


Toutes les constructions de logiciels ci-dessus font un bon travail de résolution de notre problème (y compris le cycle impératif). Ceux qui utilisent la méthode .reduce() ont quelque chose en commun. Ils sont basés sur la division du problème en petits fragments. Ces fragments sont ensuite assemblés de différentes manières. En analysant ces solutions, vous remarquerez peut-être que nous parcourons le tableau trois fois. On a l'impression que c'est inefficace. Ce serait bien s'il y avait un moyen de traiter le tableau et de retourner le résultat en une seule passe. Cette méthode existe, mais son application nécessitera le recours aux mathématiques.

Afin de calculer la valeur moyenne des éléments du tableau en un seul passage, nous avons besoin d'une nouvelle méthode. Vous devez trouver un moyen de calculer la moyenne en utilisant la moyenne précédemment calculée et la nouvelle valeur. Nous recherchons cette méthode en utilisant l'algèbre.

La valeur moyenne de n nombres peut être trouvée en utilisant cette formule:


Afin de connaître le nombre moyen de n + 1 , la même formule fera l'affaire, mais dans une entrée différente:


Cette formule est la même que celle-ci:


Et la même chose que celle-ci:


Si vous convertissez cela un peu, vous obtenez ce qui suit:


Si vous ne voyez pas l'intérêt de tout cela, alors ça va. Le résultat de toutes ces transformations est qu'à l'aide de la dernière formule, nous pouvons calculer la valeur moyenne au cours d'une seule traversée du tableau. Pour ce faire, vous devez connaître la valeur de l'élément actuel, la valeur moyenne calculée à l'étape précédente et le nombre d'éléments. De plus, la plupart des calculs peuvent être effectués dans la fonction réducteur:

 //      // ---------------------------------------------------------------------------- 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); 

Grâce à cette approche, la valeur nécessaire peut être trouvée en contournant le tableau une seule fois. D'autres approches utilisent une passe pour filtrer le tableau, une autre pour en extraire les données nécessaires et une autre pour trouver la somme des valeurs des éléments. Ici, tout rentre dans un passage à travers le tableau.

Veuillez noter que cela ne rend pas nécessairement les calculs plus efficaces. Avec cette approche, plus de calculs doivent être effectués. Lorsque chaque nouvelle valeur arrive, nous effectuons les opérations de multiplication et de division, ce faisant pour maintenir la valeur moyenne actuelle dans l'état actuel. Dans d'autres solutions à ce problème, nous divisons un numéro en un autre une seule fois - à la fin du programme. Mais cette approche est beaucoup plus efficace en termes d'utilisation de la mémoire. Les tableaux intermédiaires ne sont pas utilisés ici, par conséquent, nous devons stocker en mémoire uniquement un objet avec deux valeurs.

. . , . . , , .

?


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

, - , . , ? . . — .


:

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

, -, ? — . - — , :

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

! JavaScript-?

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


All Articles