这是Enum.group_by/3
中Elixir的Github实现:
def group_by(enumerable, key_fun, value_fun \\ fn x -> x end)
def group_by(enumerable, key_fun, value_fun) when is_function(key_fun) do
reduce(reverse(enumerable), %{}, fn entry, acc ->
key = key_fun.(entry)
value = value_fun.(entry)
case acc do
%{^key => existing} -> Map.put(acc, key, [value | existing])
%{} -> Map.put(acc, key, [value])
end
end)
end
为什么将reverse/1
函数应用于enumerable
?
答案 0 :(得分:2)
这是为了保留已分组项目的顺序。
以下是使用标准group_by
的示例:
Enum.group_by(["aa", "ab", "ac", "ba", "bb", "bc"], &String.first/1)
# %{"a" => ["aa", "ab", "ac"], "b" => ["ba", "bb", "bc"]}
如果我们在自定义实现中删除了reverse
命令:
Example.no_reverse_group_by(["aa", "ab", "ac", "ba", "bb", "bc"], &String.first/1)
# %{"a" => ["ac", "ab", "aa"], "b" => ["bc", "bb", "ba"]}
您会看到分组元素"ac", "ab", "aa"
的内部顺序与原始顺序"aa", "ab", "ac"
的相反。
原因是因为Map.put(acc, key, [value | existing])
通过将遇到的每个value
元素添加到existing
元素列表的开头来建立组。与原始可枚举相比,这将以相反的顺序建立项目组。
添加到列表的速度很快,但是添加到列表的末尾则需要遍历整个列表。因此,为了使算法能够使用有效的[value | existing]
前置操作和来确保组中项目的顺序与原始可枚举的顺序相同,必须首先反转可枚举。