web-dev-qa-db-ja.com

Elixir-ループしてマップに追加する

C#で作成したコードからElixirで何かを再構築しています。

かなりハッキングされていましたが、完全に動作します(ただし、Linux上ではないため、再構築します)。

基本的には、いくつかのRSSフィードをチェックし、新しいコンテンツがあるかどうかを確認しました。これはコードです:

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)

エリクサーで列挙を効果的に行うにはどうすればいいのだろうか。また、「歴史的」に物事を追加する私のまさにプロセスは、反機能的なプログラミングであると思われます。

明らかに、最初のステップはURLのリストを宣言することでしたが、それを超えると、列挙のアイデアが頭を混乱させます。誰かが私を助けてくれますか?ありがとう。

35
William Dunne

これは素晴らしい挑戦であり、解決することは間違いなく関数型プログラミングの洞察を与えてくれます。

関数型言語でのこのような問題の解決策は、通常reduce(しばしばfoldと呼ばれます)です。簡単な回答から始めます(直接の翻訳ではありません)が、気軽にフォローアップをお願いします。

通常、以下のアプローチは関数型プログラミング言語では機能しません。

_map = %{}
Enum.each [1, 2, 3], fn x ->
  Map.put(map, x, x)
end
map
_

データ構造を変更できないため、最後のマップは空のままです。 Map.put(map, x, x)を呼び出すたびに、新しいマップが返されます。そのため、各列挙後に新しいマップを明示的に取得する必要があります。

Reduceを使用してElixirでこれを実現できます。

_map = Enum.reduce [1, 2, 3], %{}, fn x, acc ->
  Map.put(acc, x, x)
end
_

Reduceは、前の関数の結果を次のアイテムのアキュムレーターとして出力します。上記のコードを実行すると、変数mapは_%{1 => 1, 2 => 2, 3 => 3}_になります。

これらの理由により、列挙でeachを使用することはほとんどありません。代わりに、 Enumモジュール の関数を使用します。これは幅広い操作をサポートし、他のオプションがない場合は最終的にreduceにフォールバックします。

編集:質問に答えてコードのより直接的な翻訳を行うために、これはあなたが行くようにマップをチェックして更新するためにできることです:

_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
_

実際、ここではセットの方が良いので、これをリファクタリングして、プロセスでセットを使用しましょう。

_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
_
86
José Valim

これも役立つかもしれません:

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"

出力:

iex(31)> c "count_animals.exs"
JEA: begin
%{"cats" => 1, "ducks" => 10, "geese" => 18}
JEA: end
[]

私はエリクサーを学んでいるだけなので、上記は間違いなく最適ではありませんが、願わくは少し有益です。

0
James Anderson

私もElixirの初心者ですが、パターンマッチングと再帰を使用するキュートでシンプルなソリューションを紹介します。

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 

そして、マップしたいリストと空のマップだけで関数を呼び出します

> YourModule.reduce_list(["one", "two", "three"], %{})
  %{"one" => "Done", "three" => "Done", "two" => "Done"}
0
Zemuldo