这是一个很好的挑战,解决它肯定会让您对函数式编程有所了解。
函数式语言中此类问题的解决方案通常是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