2015-04-28 30 views
29

Przebudowuję coś w eliksiru z kodu, który napisałem w C#.Eliksir - Zapętlanie i dodawanie do mapy

To było dość zhakowane razem, ale działa idealnie (chociaż nie na Linuksie, a więc na odbudowie).

Zasadniczo sprawdzono niektóre kanały RSS i sprawdzono, czy są jakieś nowe treści. To jest kod:

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) 

Zastanawiam się, w jaki sposób mogę efektywnie wykonywać wyliczanie w eliksiru. Wydaje się również, że moim samym procesem dodawania rzeczy do "historycznego" jest programowanie antyfunkcjonalne.

Oczywiście pierwszym krokiem była deklaracja mojej listy adresów URL, ale poza tym pomysł wyliczeniowy jest kłopotliwy z moją głową. Czy ktoś może mi pomóc? Dzięki.

Odpowiedz

71

Jest to miłe wyzwanie, które trzeba rozwiązać, a na pewno da wgląd w funkcjonalne programowanie.

Rozwiązaniem dla takich problemów w językach funkcjonalnych jest zwykle reduce (często nazywane fold). Zacznę od krótkiej odpowiedzi (a nie tłumaczenia bezpośredniego), ale nie krępuj się poprosić o kontynuację.

Poniższa podejście zazwyczaj nie działa w językach programowania funkcjonalnych:

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

Mapa na końcu wciąż będzie pusty, ponieważ nie możemy mutować struktury danych. Za każdym razem, gdy wywołasz Map.put(map, x, x), zwróci ona nową mapę. Musimy więc jawnie pobrać nową mapę po każdym wyliczeniu.

Możemy to osiągnąć w Elixir użyciu zmniejszają:

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

Zmniejszyć wyemituje wynik poprzedniej funkcji jako akumulatora do następnego elementu. Po uruchomieniu powyższego kodu zmienną będzie %{1 => 1, 2 => 2, 3 => 3}.

Z tych powodów rzadko używamy each podczas wyliczenia. Zamiast tego korzystamy z funkcji w the Enum module, które obsługują szeroki zakres operacji, ostatecznie wracając do reduce, gdy nie ma innej opcji.

EDIT: aby odpowiedzieć na pytania i przejść na bardziej bezpośrednim tłumaczeniu kodu, to co można zrobić, aby sprawdzić i zaktualizować 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 

W rzeczywistości, zestaw będzie lepiej tutaj, więc niech to byłaby to i użyć zestawu w procesie:

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

Okay, niesamowite, dzięki, że pomaga.Jednak nadal nie wiem, jak poradziłbym sobie z sprawdzaniem, czy przedmiot jest nowy (nie na mapie) z tym podejściem? To coś, co dzieje się dość często i wolałbym nie używać czegoś takiego jak DB, chociaż chciałbym, gdybym musiał. Ma jednak wiele sensu, dziękuję. –

+0

Co powiesz na połączenie dwóch funkcji z modułu Enum: member? i filtrować. W ten sposób możesz skompilować listę wszystkich elementów, które nie są członkami istniejącej listy, a następnie zrobić coś. – GavinBrelstaff

+0

Jak dodać do filtra? Ilekroć znajdzie unikalny post, dodaje go do listy (nie jest już unikalny) i wykonuje z nią funkcję. Czy funkcja może wywoływać samą siebie? –

0

to może pomóc też:

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" 

wyjściowa:

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

Ja tylko nauka Elixir więc powyższe jest niewątpliwie suboptimal, ale mam nadzieję, że nieco pouczające.