Erlang Thursday – lists:flatmap/2

今天的Erlang Thursday讲的是 lists:flatmap/2.

选择这个函数来讲是因为这个星期早些时候我做的一些 Elixir 练习,在这些练习中我使用 Enum.flat_map/2 函数而它却不是像我期待的那样起作用,这个情况让我很困惑。

于是我回到Erlang来研究 lists:flatmap/2 的执行情况,然后在完全了解了该函数在Erlang里没有正确执行的原因后,我终于明白了这个函数的运行模式,同时我也意识到原来我对这个函数有错误的认识,所以是时候回来说清楚 lists:flatmap/2 实际上做了些什么。

不知何故,我曾误以为 lists:flatmap/2 就是接收一个任意嵌套深度的列表做为入参,并将他们的扁平化后每个元素都应用在map函数上,就相当于下面的例子:

1
2
lists:map(fun(X) -> X * X end, lists:flatten([1, [[2, [3]], 4]])).
% [1,4,9,16]

即使更近一步阅读 Ruby 文档,甚至在尝试 Ruby 的 flat_map 函数后,很显然我依然完全不理解它上如何工作的,所以是时候仔细阅读 Erlang 文档了。

Erlang文档说明 lists:flatmap/2 第一个入参是一个函数,这个入参函数接收一个类型A的元素然后返回一个由类型B的元素组成的列表;lists:flatmap/2 的第二个入参是一个由类型A的元素组成的列表。

我已经困惑于它到底是如何工作的,而且文档也没说明,直到我看到文档的一部分描述了 lists:flatmap/2 的工作原理入下面的定义一样:

1
2
flatmap(Fun, List1) ->
append(map(Fun, List1)).

这个定义一下子让我明白了 lists:flatmap/2 到底是如何执行的。以前在我的脑海里,我认为首先是扁平化列表,然后在将各个列表元素应用于map函数上,但是实际上,它首先将各个列表元素应用于map函数上,然后再简单地扁平化,而且仅仅是做了一个层级的扁平化。

1
2
3
4
5
lists:flatmap(fun({Item, Count}) ->
lists:duplicate(Count, Item)
end,
[{a, 1}, {b, 2}, {'C', 3}, {'_d_', 4}]).
% [a,b,b,'C','C','C','_d_','_d_','_d_','_d_']

而如果我们将相同的数据先传给map函数,然后再将其结果传给append函数,我们会得到相同的结果。

1
2
3
4
5
6
lists:append(
lists:map(fun({Item, Count}) ->
lists:duplicate(Count, Item)
end,
[{a, 1}, {b, 2}, {'C', 3}, {'_d_', 4}])).
% [a,b,b,'C','C','C','_d_','_d_','_d_','_d_']

同时我们更进一步了解到,lists:flatmap/2 甚至不将列表扁平化,而只是简单地将map函数返回的列表链接起来。如下面的例子,最后的结果仍然是一个嵌套的列表结构,而不是只有一个层级的列表。

1
2
lists:flatmap(fun(X) -> [X, [X]] end, [a, b, c, d]).
% [a,[a],b,[b],c,,d,[d]]

希望本文能让你不要陷入我曾经的困惑。

原文链接: https://www.proctor-it.com/erlang-thursday-lists-flatmap-2/