2015-04-28 9 views
29

Sto ricostruendo qualcosa in Elixir da un codice che ho creato in C#.Elixir - Looping e aggiunta alla mappa

E 'stato abbastanza hackerato insieme, ma funziona perfettamente (anche se non su Linux, quindi ricostruire).

In sostanza, ciò che ha fatto è stato controllare alcuni feed RSS e vedere se c'erano nuovi contenuti. Questo è il codice:

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) 

Mi chiedo come posso fare Enumeration in modo efficace in Elixir. Inoltre, sembra che il mio stesso processo di aggiunta di cose a "storico" sia la programmazione anti-funzionale.

Ovviamente il primo passo è stato dichiarare la mia lista di URL, ma oltre a ciò l'idea dell'enumerazione mi sta confondendo la testa. Qualcuno potrebbe aiutarmi? Grazie.

risposta

71

Questa è una bella sfida per avere e risolverlo vi darà sicuramente alcune informazioni sulla programmazione funzionale.

La soluzione per tali problemi in lingue funzionali è in genere reduce (spesso chiamata fold). Inizierò con una risposta breve (e non una traduzione diretta), ma mi sento libero di chiedere un seguito.

L'approccio seguito in genere non funziona nei linguaggi di programmazione funzionali:

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

La mappa alla fine sarà ancora vuoto perchè non siamo in grado di mutare le strutture di dati. Ogni volta che chiami il numero Map.put(map, x, x), verrà restituita una nuova mappa. Quindi dobbiamo recuperare esplicitamente la nuova mappa dopo ogni enumerazione.

Possiamo raggiungere questo obiettivo in Elixir utilizzando ridurre:

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

Ridurre emetterà il risultato della funzione precedente come accumulatore per la voce successiva. Dopo aver eseguito il codice sopra, la variabile map sarà %{1 => 1, 2 => 2, 3 => 3}.

Per questi motivi, raramente si utilizza each sull'enumerazione. Invece, utilizziamo le funzioni in the Enum module, che supportano una vasta gamma di operazioni, eventualmente ricadendo su reduce quando non ci sono altre opzioni.

EDIT: per rispondere alle domande e passare attraverso una traduzione più diretta del codice, questa cosa si può fare per controllare e aggiornare la mappa come si va:

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 realtà, un insieme sarebbe meglio qui, quindi, rifattorizziamo questo e utilizzare un set nel processo:

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

Ok, fantastico, grazie che aiuta.Tuttavia, non so ancora come gestirò il controllo se un oggetto è nuovo (non nella mappa) con questo approccio? È qualcosa che accade abbastanza spesso e preferirei non usare qualcosa come un DB, anche se lo farei se dovessi farlo. Tuttavia, ha molto senso, grazie. –

+0

Che ne dici di combinare due funzioni dal modulo Enum: membro? e filtro. In questo modo è possibile compilare l'elenco di tutti gli elementi, non un membro della lista esistente, quindi fare qualcosa. – GavinBrelstaff

+0

Come aggiungeresti al filtro? Ogni volta che trova un post univoco, lo aggiunge all'elenco (non più unico) e svolge una funzione con esso. È possibile che una funzione si chiami da sola? –

0

questo potrebbe aiutare anche:

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" 

uscita:

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

sto solo imparando Elixir in modo quanto sopra è senza dubbio non ottimale, ma, si spera un po 'informativa.