Je reconstruis quelque chose dans Elixir à partir d'un code que j'ai construit en C #.
Il a été assez piraté ensemble, mais fonctionne parfaitement (bien que pas sur Linux, donc reconstruire).
Essentiellement, cela a consisté à vérifier certains flux RSS et à voir s'il y avait du nouveau contenu. Voici le code:
Map historic (URL as key, post title as value).
List<string> blogfeeds
while true
for each blog in blogfeeds
List<RssPost> posts = getposts(blog)
for each post in posts
if post.url is not in historic
dothing(post)
historic.add(post)
Je me demande comment je peux faire le dénombrement efficacement dans Elixir. De plus, il semble que mon processus même d'ajout de choses à "historique" est une programmation anti-fonctionnelle.
Évidemment, la première étape consistait à déclarer ma liste d'URL, mais au-delà, l'idée d'énumération me dérange la tête. Est-ce que quelqu'un peut m'aider? Merci.
C'est un beau défi à relever et le résoudre vous donnera certainement un aperçu de la programmation fonctionnelle.
La solution à ces problèmes dans les langages fonctionnels est généralement reduce
(souvent appelée fold
). Je vais commencer par une réponse courte (et non une traduction directe) mais n'hésitez pas à demander un suivi.
L'approche ci-dessous ne fonctionne généralement pas dans les langages de programmation fonctionnels:
map = %{}
Enum.each [1, 2, 3], fn x ->
Map.put(map, x, x)
end
map
La carte à la fin sera toujours vide car nous ne pouvons pas muter les structures de données. Chaque fois que vous appelez Map.put(map, x, x)
, il renverra une nouvelle carte. Nous devons donc récupérer explicitement la nouvelle carte après chaque énumération.
Nous pouvons y parvenir dans Elixir en utilisant réduire:
map = Enum.reduce [1, 2, 3], %{}, fn x, acc ->
Map.put(acc, x, x)
end
Réduire émettra le résultat de la fonction précédente comme accumulateur pour l'élément suivant. Après avoir exécuté le code ci-dessus, la variable map
sera %{1 => 1, 2 => 2, 3 => 3}
.
Pour ces raisons, nous utilisons rarement each
sur l'énumération. Au lieu de cela, nous utilisons les fonctions dans le module Enum
, qui prennent en charge un large éventail d'opérations, pour finalement revenir à reduce
lorsqu'il n'y a pas d'autre option.
EDIT: pour répondre aux questions et passer par une traduction plus directe du code, voici ce que vous pouvez faire pour vérifier et mettre à jour la carte au fur et à mesure:
Enum.reduce blogs, %{}, fn blog, history ->
posts = get_posts(blog)
Enum.reduce posts, history, fn post, history ->
if Map.has_key?(history, post.url) do
# Return the history unchanged
history
else
do_thing(post)
Map.put(history, post.url, true)
end
end
end
En fait, un ensemble serait mieux ici, alors refactorisons ceci et utilisons un ensemble dans le processus:
def traverse_blogs(blogs) do
Enum.reduce blogs, HashSet.new, &traverse_blog/2
end
def traverse_blog(blog, history) do
Enum.reduce get_posts(blog), history, &traverse_post/2
end
def traverse_post(post, history) do
if post.url in history do
# Return the history unchanged
history
else
do_thing(post)
HashSet.put(history, post.url)
end
end
Cela pourrait aussi aider:
count_animals_in_area = fn (area, acc) ->
acc = case Map.has_key?(area, "duck") do
true ->
Map.put(acc, "ducks", (acc["ducks"] + area["duck"]))
false ->
acc
end
acc = case Map.has_key?(area, "goose") do
true ->
Map.put(acc, "geese", (acc["geese"] + area["goose"]))
false ->
acc
end
acc = case Map.has_key?(area, "cat") do
true ->
Map.put(acc, "cats", (acc["cats"] + area["cat"]))
false ->
acc
end
acc
end
count_animals_in_areas = fn(areas) ->
acc = %{ "ducks" => 0,
"geese" => 0,
"cats" => 0 }
IO.inspect Enum.reduce areas, acc, count_animals_in_area
end
t1 = [ %{"duck" => 3, "goose" => 4, "cat" => 1},
%{"duck" => 7, "goose" => 2},
%{"goose" => 12}]
IO.puts "JEA: begin"
count_animals_in_areas.(t1)
IO.puts "JEA: end"
Sortie:
iex(31)> c "count_animals.exs"
JEA: begin
%{"cats" => 1, "ducks" => 10, "geese" => 18}
JEA: end
[]
J'apprends juste Elixir donc ce qui précède est sans aucun doute sous-optimal, mais, espérons-le, légèrement informatif.
Je suis également nouveau sur Elixir, mais voici une solution mignonne et simple qui utilise la correspondance de motifs et la récursivité.
defmodule YourModule do
def reduce_list([], reduced) do reduced end
def reduce_list([first | rest ], reduced) do
# Do what you need to do here and call the function again
# with remaining list items and updated map.
reduce_list(rest, Map.put(reduced, first, "Done"))
end
end
Et appelez la fonction avec juste la liste que vous souhaitez mapper et une carte vide
> YourModule.reduce_list(["one", "two", "three"], %{})
%{"one" => "Done", "three" => "Done", "two" => "Done"}