首页 > 解决方案 > 为什么 Elixir 的 group_by 在实现中使用了反向函数?

问题描述

这是来自Github的 Elixir 的Enum.group_by/3实现:

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?

标签: elixir

解决方案


这是为了保留分组项目的顺序。

这是使用标准的示例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]) 构建组。与原始可枚举相比,这会以相反的顺序建立项目组。valueexisting

添加到列表的速度很快,但添加到列表的末尾需要遍历整个列表。因此,为了使算法能够使用高效的[value | existing]前置操作,确保组中项目的顺序与原始可枚举相同,首先需要反转可枚举。


推荐阅读