我如何同时映射和group_by?

use*_*730 7 elixir

举个例子,假设我有collection很多对{first, second}.使用分组这些对

Enum.group_by(collection, fn {first, second} -> first end)
Run Code Online (Sandbox Code Playgroud)

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


一般来说,给定一个可枚举的,我想分组提供一个关键提取器一个值映射器,以便我可以确定将什么放入生成Map的值中.即,我想要类似的东西

map_group_by(
  collection,
  fn {_first, second} -> second end,
  fn {first, _second} -> first end
)
Run Code Online (Sandbox Code Playgroud)

collection分组之前将值映射到哪里,而键映射器仍然在原始元素上运行.

标准库中有这样的功能吗?如果没有,最实用的方法是什么?


我知道我可以做点什么

Enum.reduce(
  collection,
  %{},
  fn({key, value}, acc) -> Dict.update(acc, key, [value], &([value | &1])) end
)
Run Code Online (Sandbox Code Playgroud)

但这看起来很笨拙并且[value]先发制人地创建了名单(实际上是真的吗?).有没有更简洁有效的方法?

Jus*_*age 6

要回答你的问题,我认为没有本地功能可以做到这一点.

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

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

iex> list = [{:a, 1}, {:b, 2}]
[a: 1, b: 2]
iex> list == [a: 1, b: 2]
true
Run Code Online (Sandbox Code Playgroud)

因此,考虑到这一点,它很容易使用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])
Run Code Online (Sandbox Code Playgroud)

将打印出来:

[a: [3, 2], b: [3]]
Run Code Online (Sandbox Code Playgroud)

编辑:更快的版本:

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])
Run Code Online (Sandbox Code Playgroud)


use*_*730 6

从Elixir 1.3开始,现在Enum.group_by/3有一个mapper_fun参数,它解决了这个问题.


过时的答案:

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

Enum.group_by(enumerable, &elem(&1, 0), &elem(&1, 1))
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)

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

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