Подтвердить что ты не робот

Эликсир - Прокрутка и добавление к карте

Я перестраиваю что-то в 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)

Мне интересно, как я могу сделать Enumeration эффективно в Elixir. Кроме того, похоже, что мой процесс добавления вещей в "исторический" - это антифункциональное программирование.

Очевидно, что первым шагом было объявление моего списка URL-адресов, но помимо этого идея перечисления возится с моей головой. Может ли кто-нибудь помочь мне? Спасибо.

4b9b3361

Ответ 1

Это хороший вызов, чтобы иметь и решать его, безусловно, даст вам некоторое представление о функциональном программировании.

Решение таких задач в функциональных языках обычно reduce (часто называемое fold). Я начну с короткого ответа (а не прямого перевода), но не стесняйтесь просить о последующих действиях.

Приведенный ниже подход обычно не работает в языках функционального программирования:

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

Карта в конце будет по-прежнему пуста, потому что мы не можем мутировать структуры данных. Каждый раз, когда вы вызываете Map.put(map, x, x), он возвращает новую карту. Поэтому нам нужно явно получить новую карту после каждого перечисления.

Мы можем достичь этого в Elixir, используя сокращение:

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

Уменьшение будет выдавать результат предыдущей функции в качестве аккумулятора для следующего элемента. После выполнения кода выше, переменная map будет %{1 => 1, 2 => 2, 3 => 3}.

По этим причинам мы редко используем each при перечислении. Вместо этого мы используем функции в модуле Enum, которые поддерживают широкий диапазон операций, в конце концов возвращаясь к reduce, когда нет другой опции.

EDIT: чтобы ответить на вопросы и перейти к более прямому переводу кода, это то, что вы можете сделать, чтобы проверить и обновить карту по ходу:

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

Ответ 2

это тоже может помочь:

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
[]

Я просто изучаю эликсир, поэтому выше, несомненно, субоптимальный, но, надеюсь, немного информативный.