举个例子,我们说我有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}}列表(实际上是真的吗?)。是否有更简洁有效的更好方法?
答案 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
来电,这不是我问题中的示例。这可确保元素顺序保留在结果值列表中。如果您不需要保留该顺序(就像我在我的情况下所做的那样,我只想从结果中进行采样),它可以被删除。