Elixir - Énumérables

Un énumérable est un objet qui peut être énuméré. «Énuméré» signifie compter les membres d'un ensemble / collection / catégorie un par un (généralement dans l'ordre, généralement par nom).

Elixir fournit le concept d'énumérables et le module Enum pour travailler avec eux. Les fonctions du module Enum sont limitées, comme son nom l'indique, à l'énumération des valeurs dans les structures de données. Un exemple de structure de données énumérables est une liste, un tuple, une carte, etc. Le module Enum nous fournit un peu plus de 100 fonctions pour traiter les énumérations. Nous discuterons de quelques fonctions importantes dans ce chapitre.

Toutes ces fonctions prennent un énumérable comme premier élément et une fonction comme second et travaillent dessus. Les fonctions sont décrites ci-dessous.

tout?

Quand nous utilisons all? fonction, la collection entière doit être évaluée à vrai sinon faux sera retourné. Par exemple, pour vérifier si tous les éléments de la liste sont des nombres impairs, alors.

res = Enum.all?([1, 2, 3, 4], fn(s) -> rem(s,2) == 1 end) 
IO.puts(res)

Lorsque le programme ci-dessus est exécuté, il produit le résultat suivant -

false

En effet, tous les éléments de cette liste ne sont pas étranges.

tout?

Comme son nom l'indique, cette fonction renvoie true si un élément de la collection est évalué à true. Par exemple -

res = Enum.any?([1, 2, 3, 4], fn(s) -> rem(s,2) == 1 end)
IO.puts(res)

Lorsque le programme ci-dessus est exécuté, il produit le résultat suivant -

true

tronçon

Cette fonction divise notre collection en petits morceaux de la taille fournie comme deuxième argument. Par exemple -

res = Enum.chunk([1, 2, 3, 4, 5, 6], 2)
IO.puts(res)

Lorsque le programme ci-dessus est exécuté, il produit le résultat suivant -

[[1, 2], [3, 4], [5, 6]]

chaque

Il peut être nécessaire de parcourir une collection sans produire de nouvelle valeur, dans ce cas, nous utilisons la each fonction -

Enum.each(["Hello", "Every", "one"], fn(s) -> IO.puts(s) end)

Lorsque le programme ci-dessus est exécuté, il produit le résultat suivant -

Hello
Every
one

carte

Pour appliquer notre fonction à chaque élément et produire une nouvelle collection, nous utilisons la fonction map. C'est l'une des constructions les plus utiles en programmation fonctionnelle car elle est assez expressive et courte. Prenons un exemple pour comprendre cela. Nous doublerons les valeurs stockées dans une liste et la stockerons dans une nouvelle listeres -

res = Enum.map([2, 5, 3, 6], fn(a) -> a*2 end)
IO.puts(res)

Lorsque le programme ci-dessus est exécuté, il produit le résultat suivant -

[4, 10, 6, 12]

réduire

le reduceLa fonction nous aide à réduire notre énumérable à une valeur unique. Pour ce faire, nous fournissons un accumulateur optionnel (5 dans cet exemple) à passer dans notre fonction; si aucun accumulateur n'est fourni, la première valeur est utilisée -

res = Enum.reduce([1, 2, 3, 4], 5, fn(x, accum) -> x + accum end)
IO.puts(res)

Lorsque le programme ci-dessus est exécuté, il produit le résultat suivant -

15

L'accumulateur est la valeur initiale transmise au fn. À partir du deuxième appel, la valeur renvoyée par l'appel précédent est transmise en tant que cumul. Nous pouvons également utiliser réduire sans l'accumulateur -

res = Enum.reduce([1, 2, 3, 4], fn(x, accum) -> x + accum end)
IO.puts(res)

Lorsque le programme ci-dessus est exécuté, il produit le résultat suivant -

10

uniq

La fonction uniq supprime les doublons de notre collection et renvoie uniquement l'ensemble des éléments de la collection. Par exemple -

res = Enum.uniq([1, 2, 2, 3, 3, 3, 4, 4, 4, 4])
IO.puts(res)

Lors de l'exécution du programme ci-dessus, il produit le résultat suivant -

[1, 2, 3, 4]

Évaluation impatiente

Toutes les fonctions du module Enum sont impatientes. De nombreuses fonctions attendent un énumérable et renvoient une liste. Cela signifie que lors de l'exécution de plusieurs opérations avec Enum, chaque opération va générer une liste intermédiaire jusqu'à ce que nous atteignions le résultat. Prenons l'exemple suivant pour comprendre cela -

odd? = &(odd? = &(rem(&1, 2) != 0) 
res = 1..100_000 |> Enum.map(&(&1 * 3)) |> Enum.filter(odd?) |> Enum.sum 
IO.puts(res)

Lorsque le programme ci-dessus est exécuté, il produit le résultat suivant -

7500000000

L'exemple ci-dessus a un pipeline d'opérations. Nous commençons par une plage puis multiplions chaque élément de la plage par 3. Cette première opération va maintenant créer et retourner une liste avec 100_000 éléments. Ensuite, nous conservons tous les éléments impairs de la liste, générant une nouvelle liste, maintenant avec 50_000 éléments, puis nous additionnons toutes les entrées.

le |> Le symbole utilisé dans l'extrait ci-dessus est le pipe operator: il prend simplement la sortie de l'expression sur son côté gauche et le passe comme premier argument à l'appel de fonction sur son côté droit. C'est similaire à Unix | opérateur. Son objectif est de mettre en évidence le flux de données transformé par une série de fonctions.

Sans le pipe opérateur, le code semble compliqué -

Enum.sum(Enum.filter(Enum.map(1..100_000, &(&1 * 3)), odd?))

Nous avons de nombreuses autres fonctions, cependant, seules quelques-unes importantes ont été décrites ici.