Elixir - 循环并添加到地图
Elixir - Looping through and adding to map
我正在用我在 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)
我想知道如何在 Elixir 中有效地进行枚举。此外,我向 "historic" 添加东西的过程似乎是反功能编程。
显然,第一步是声明我的 URL 列表,但除此之外,枚举的想法让我头疼。有人可以帮帮我吗?谢谢
这是一个很好的挑战,解决它肯定会让您对函数式编程有所了解。
函数式语言中此类问题的解决方案通常是reduce
(通常称为fold
)。我将从一个简短的回答开始(而不是直接翻译),但请随时要求跟进。
下面的方法通常不适用于函数式编程语言:
map = %{}
Enum.each [1, 2, 3], fn x ->
Map.put(map, x, x)
end
map
最后的映射仍然是空的,因为我们不能改变数据结构。每次调用 Map.put(map, x, x)
,它都会 return 一张新地图。所以我们需要在每次枚举后显式地检索新地图。
我们可以在 Elixir 中使用 reduce 实现这一点:
map = Enum.reduce [1, 2, 3], %{}, fn x, acc ->
Map.put(acc, x, x)
end
Reduce 将发出前一个函数的结果作为下一项的累加器。在 运行 上面的代码之后,变量 map
将是 %{1 => 1, 2 => 2, 3 => 3}
.
出于这些原因,我们很少在枚举中使用 each
。相反,我们使用 the Enum
module 中支持广泛操作的函数,最终在没有其他选择时退回到 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
其实这里用set会更好,所以我们重构一下,在这个过程中使用set:
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
这也可能有帮助:
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
[]
我刚开始学习 Elixir,所以上面的内容无疑不是最理想的,但是,希望能提供一些信息。
我也是 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"}
我正在用我在 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)
我想知道如何在 Elixir 中有效地进行枚举。此外,我向 "historic" 添加东西的过程似乎是反功能编程。
显然,第一步是声明我的 URL 列表,但除此之外,枚举的想法让我头疼。有人可以帮帮我吗?谢谢
这是一个很好的挑战,解决它肯定会让您对函数式编程有所了解。
函数式语言中此类问题的解决方案通常是reduce
(通常称为fold
)。我将从一个简短的回答开始(而不是直接翻译),但请随时要求跟进。
下面的方法通常不适用于函数式编程语言:
map = %{}
Enum.each [1, 2, 3], fn x ->
Map.put(map, x, x)
end
map
最后的映射仍然是空的,因为我们不能改变数据结构。每次调用 Map.put(map, x, x)
,它都会 return 一张新地图。所以我们需要在每次枚举后显式地检索新地图。
我们可以在 Elixir 中使用 reduce 实现这一点:
map = Enum.reduce [1, 2, 3], %{}, fn x, acc ->
Map.put(acc, x, x)
end
Reduce 将发出前一个函数的结果作为下一项的累加器。在 运行 上面的代码之后,变量 map
将是 %{1 => 1, 2 => 2, 3 => 3}
.
出于这些原因,我们很少在枚举中使用 each
。相反,我们使用 the Enum
module 中支持广泛操作的函数,最终在没有其他选择时退回到 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
其实这里用set会更好,所以我们重构一下,在这个过程中使用set:
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
这也可能有帮助:
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
[]
我刚开始学习 Elixir,所以上面的内容无疑不是最理想的,但是,希望能提供一些信息。
我也是 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"}