我如何同时映射和group_by?

时间:2016-02-15 18:57:44

标签: elixir

举个例子,我们说我有collection{first, second}Enum.group_by(collection, fn {first, second} -> first end) 。使用

对这些对进行分组
Map

将导致second,其键由传递的匿名函数确定。它的值是对的集合。 但是,我希望其值包含对Map元素。

一般来说,给定一个可枚举的,我想分组提供一个关键提取器一个值映射器,以便我可以确定将哪些内容放入生成的map_group_by( collection, fn {_first, second} -> second end, fn {first, _second} -> first end ) &#中39; s值。即,我想要像

这样的东西
collection

其中Enum.reduce( collection, %{}, fn({key, value}, acc) -> Dict.update(acc, key, [value], &([value | &1])) end ) 的值在被分组之前被映射,但是键映射器仍然在原始元素上运行。

标准库中是否有这样的功能?如果没有,实现这一目标的最惯用方法是什么?

我知道我可以做类似

的事情
[value]

但这看起来很笨拙并且先发制人地创建{{1}}列表(实际上是真的吗?)。是否有更简洁有效的更好方法?

2 个答案:

答案 0 :(得分:6)

要回答您的问题,我认为没有本地功能可以执行此操作。

但我会给你解决方案(免责声明:我是Elixir的新手)。

首先,重要的是要注意,正如您在Elixir Docs中看到的那样,元组列表与键值列表相同:

iex> list = [{:a, 1}, {:b, 2}]
[a: 1, b: 2]
iex> list == [a: 1, b: 2]
true

因此,考虑到这一点,它可以很容易地使用Enum.map

这确实可以让它通过两次,但它看起来比你拥有的更清洁:

defmodule EnumHelpers do
  def map_col(lst) do
    lst
    |> Enum.group_by(fn {x, _} -> x end)
    |> Enum.map(fn {x, y} -> {x, Dict.values y} end)
  end
end

IO.inspect EnumHelpers.map_col([a: 2, a: 3, b: 3])

将打印出来:

[a: [3, 2], b: [3]]

修改:更快的版本

defmodule EnumHelpers do

  defp group_one({key, val}, categories) do
    Dict.update(categories, key, [val], &[val|&1])
  end

  def map_col_fast(coll) do
    Enum.reduce(coll, %{}, &group_one/2)
  end
end

IO.inspect EnumHelpers.map_col_fast([a: 2, a: 3, b: 3])

答案 1 :(得分:5)

自Elixir 1.3以来,现在Enum.group_by/3采用mapper_fun参数,这正好解决了这个问题。

过时回答:

此时,标准库中没有这样的功能。我最终使用了这个:

def map_group_by(enumerable, value_mapper, key_extractor) do
  Enum.reduce(Enum.reverse(enumerable), %{}, fn(entry, categories) ->
    value = value_mapper.(entry)
    Map.update(categories, key_extractor.(entry), [value], &[value | &1])
  end)
end

然后可以这样调用(对于我的例子):

map_group_by(
  collection,
  fn {_, second} -> second end,
  fn {first, _} -> first end
)

它改编自标准库Enum.group_by。 关于[value]:我不知道编译器能够或不能优化什么,但至少这也是Enum.group_by所做的。

请注意Enum.reverse来电,这不是我问题中的示例。这可确保元素顺序保留在结果值列表中。如果您不需要保留该顺序(就像我在我的情况下所做的那样,我只想从结果中进行采样),它可以被删除。