Elixir - 循环并添加到地图
Posted
技术标签:
【中文标题】Elixir - 循环并添加到地图【英文标题】:Elixir - Looping through and adding to map 【发布时间】:2015-07-07 14:01:15 【问题描述】:我正在用我在 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 中有效地进行枚举。另外,我在“历史”中添加东西的过程似乎是反函数式编程。
显然第一步是声明我的 URL 列表,但除此之外,枚举的想法让我很头疼。有人可以帮我吗?谢谢。
【问题讨论】:
【参考方案1】:这是一个很好的挑战,解决它肯定会让您对函数式编程有所了解。
函数式语言中此类问题的解决方案通常是reduce
(通常称为fold
)。我将从一个简短的答案开始(而不是直接翻译),但随时要求跟进。
以下方法通常不适用于函数式编程语言:
map = %
Enum.each [1, 2, 3], fn x ->
Map.put(map, x, x)
end
map
最后的地图仍然是空的,因为我们不能改变数据结构。每次调用Map.put(map, x, x)
,它都会返回一张新地图。因此,我们需要在每次枚举后显式检索新地图。
我们可以在 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
【讨论】:
好的,太棒了,谢谢你的帮助。但是,我仍然不知道如何使用这种方法检查项目是否是新的(不在地图中)?它经常发生,我宁愿不使用数据库之类的东西,尽管如果我不得不这样做的话。不过,这很有意义,谢谢。 结合 Enum 模块中的两个函数如何:成员?和过滤。这样你就可以编译不是现有列表成员的所有项目的列表,然后做一些事情。 您将如何添加到过滤器中?每当它找到一个独特的帖子时,它就会将它添加到列表中(不再是唯一的)并使用它执行一个功能。函数可以调用自己吗? 我已编辑问题以获得更完整的答案! 非常感谢@José Valim【参考方案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
[]
我只是在学习 Elixir,所以上述内容无疑不是最理想的,但希望能提供一些信息。
【讨论】:
【参考方案3】:我也是 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"
【讨论】:
以上是关于Elixir - 循环并添加到地图的主要内容,如果未能解决你的问题,请参考以下文章
dialyzer 无法识别 elixir 函数并出现错误:0:unknown_function