Elixir - Looping through and adding to map

前端 未结 3 1124
再見小時候
再見小時候 2020-12-13 03:55

I\'m rebuilding something in Elixir from some code I built in C#.

It was pretty hacked together, but works perfectly (although not on Linux, hence rebuild).

相关标签:
3条回答
  • 2020-12-13 04:29

    This might help too:

    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"
    

    Output:

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

    I am just learning Elixir so the above is undoubtedly suboptimal, but, hopefully slightly informative.

    0 讨论(0)
  • 2020-12-13 04:45

    I am also new to Elixir but here is a cute and simple solution that uses pattern matching and recursion.

    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 
    

    And call the function with just the list you want to map and an empty map

    > YourModule.reduce_list(["one", "two", "three"], %{})
      %{"one" => "Done", "three" => "Done", "two" => "Done"}
    
    0 讨论(0)
  • 2020-12-13 04:47

    This is a nice challenge to have and solving it will definitely give you some insight into functional programming.

    The solution for such problems in functional languages is usually reduce (often called fold). I will start with a short answer (and not a direct translation) but feel free to ask for a follow up.

    The approach below will typically not work in functional programming languages:

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

    The map at the end will still be empty because we can't mutate data structures. Every time you call Map.put(map, x, x), it will return a new map. So we need to explicitly retrieve the new map after each enumeration.

    We can achieve this in Elixir using reduce:

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

    Reduce will emit the result of the previous function as accumulator for the next item. After running the code above, the variable map will be %{1 => 1, 2 => 2, 3 => 3}.

    For those reasons, we rarely use each on enumeration. Instead, we use the functions in the Enum module, that support a wide range of operations, eventually falling back to reduce when there is no other option.

    EDIT: to answer the questions and go through a more direct translation of the code, this what you can do to check and update the map as you go:

    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
    

    In fact, a set would be better here, so let's refactor this and use a set in the process:

    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
    
    0 讨论(0)
提交回复
热议问题