Elixir 长生不老药:在列表地图中找到最小长度列表的键
在Elixir中的列表映射中,找到与最小长度列表相关联的键的有效方法是什么。假设我有:Elixir 长生不老药:在列表地图中找到最小长度列表的键,elixir,Elixir,在Elixir中的列表映射中,找到与最小长度列表相关联的键的有效方法是什么。假设我有: z = %{a: [1, 2, 3], b: [4, 5], c: [6, 7, 8, 9]} 我知道我能做到: Enum.map z, fn {k, v} -> length(v) end 这将给我: [3, 2, 4] 但我真正需要的只是答案,即与最小值2相关联的键,当然是:b 我将在列表的动态地图上大约每秒钟运行一次,所以我希望它尽可能高效 用长生不老药做任何事情最有效的方法是: Enum.
z = %{a: [1, 2, 3], b: [4, 5], c: [6, 7, 8, 9]}
我知道我能做到:
Enum.map z, fn {k, v} -> length(v) end
这将给我:
[3, 2, 4]
但我真正需要的只是答案,即与最小值2相关联的键,当然是:b
我将在列表的动态地图上大约每秒钟运行一次,所以我希望它尽可能高效 用长生不老药做任何事情最有效的方法是:
Enum.reduce(z,{nil,0},fn
{k,v},{u,len}当len==0或len>length(v)->{k,length(v)}
_,acc->acc
(完)
#⇒{:b,2}
要仅获取:b
,模式匹配结果:
{key,{}=⇑以上⇑
或者(更糟糕的是)管道到|>Tuple.to_list()|>list.first
在这里,我们更新累加器,如果:
- 我们刚刚开始,保存的值为空,或者
- 新的长度比旧的短
Enum.reduce(z,{nil,0},fn
{k,v},{u,len}当len==0或len>length(v)->{k,length(v)}
_,acc->acc
(完)
#⇒{:b,2}
要仅获取:b
,模式匹配结果:
{key,{}=⇑以上⇑
或者(更糟糕的是)管道到|>Tuple.to_list()|>list.first
在这里,我们更新累加器,如果:
- 我们刚刚开始,保存的值为空,或者
- 新的长度比旧的短
:maps.fold
通常比Enum.reduce快一点点。这是一个比@mudasobwa的实现快10%的实现:
:maps.fold(
fn k, v, {_, min} = acc ->
l = length(v)
if min == 0 || l < min, do: {k, l}, else: acc
end,
{nil, 0},
map
)
|> elem(0)
如果你在寻找速度,
:maps.fold
通常比Enum.reduce
快一点点。这是一个比@mudasobwa的实现快10%的实现:
:maps.fold(
fn k, v, {_, min} = acc ->
l = length(v)
if min == 0 || l < min, do: {k, l}, else: acc
end,
{nil, 0},
map
)
|> elem(0)
除非绝对必要,否则我更喜欢简单而不是性能,所以如果性能不太重要,我会这样做:
map
|> Enum.min_by(fn {_k, v} -> length(v) end)
|> elem(0)
通过@Dogbert添加到基准测试中可以发现,在我的机器上,平均90µs/次迭代的速度大约慢1.5倍。这仍然有足够的空间让它每秒钟运行一次。除非绝对必要,否则我更喜欢简单而不是性能,所以如果性能不太重要,我会这样做:
map
|> Enum.min_by(fn {_k, v} -> length(v) end)
|> elem(0)
通过@Dogbert添加到基准测试中可以发现,在我的机器上,平均90µs/次迭代的速度大约慢1.5倍。这仍然有足够的空间让它每秒钟运行一次。对于任何想重复@Dogbert的基准测试的人,包括Patrick Oscity的答案:
map = for(key <- 1..1000, into: %{}, do: {key, Enum.random([[1, 2, 3], [4, 5], [6, 7, 8, 9]])})
Benchee.run(%{
"mudasobwa" => fn ->
Enum.reduce(map, {nil, 0}, fn
{k, v}, {_, len} when len == 0 or len > length(v) -> {k, length(v)}
_, acc -> acc
end)
|> elem(0)
end,
"dogbert" => fn ->
:maps.fold(
fn k, v, {_, min} = acc ->
l = length(v)
if min == 0 || l < min, do: {k, l}, else: acc
end,
{nil, 0},
map
)
|> elem(0)
end,
"oscity" => fn ->
map
|> Enum.min_by(fn {_k, v} -> length(v) end)
|> elem(0)
end
})
Benchee代码如下:
我只是加了一句
{:benchee,“~>0.12.1”}
在mix.exs中的deps下 对于希望重复@Dogbert基准的任何人,包括Patrick Oscity的答案:
map = for(key <- 1..1000, into: %{}, do: {key, Enum.random([[1, 2, 3], [4, 5], [6, 7, 8, 9]])})
Benchee.run(%{
"mudasobwa" => fn ->
Enum.reduce(map, {nil, 0}, fn
{k, v}, {_, len} when len == 0 or len > length(v) -> {k, length(v)}
_, acc -> acc
end)
|> elem(0)
end,
"dogbert" => fn ->
:maps.fold(
fn k, v, {_, min} = acc ->
l = length(v)
if min == 0 || l < min, do: {k, l}, else: acc
end,
{nil, 0},
map
)
|> elem(0)
end,
"oscity" => fn ->
map
|> Enum.min_by(fn {_k, v} -> length(v) end)
|> elem(0)
end
})
Benchee代码如下:
我只是加了一句
{:benchee,“~>0.12.1”}
在mix.exs中的deps下 如果多个列表的最小值相等怎么办?@KevinJohnson那么我在最小值相等的列表之间矛盾,但我仍然只需要一个选择。如果多个列表的最小值相等怎么办?@KevinJohnson那么我在最小值相等的列表之间矛盾,但我仍然只需要一个选项。@ThomasBrowne
first
,但要避免使用它。按说明使用匹配。|>Tuple.to_list()|>list.first()
只是|>elem(0)
@ThomasBrownefirst
,但请避免使用它。按说明使用匹配。|>Tuple.to_list()|>list.first()
只是|>元素(0)
。另外,偏差25%建议不要将10%的差异看得太严重。您是如何进行实际基准测试的?“它是内置在iex中的吗?”我用过托马斯布朗。它不是内置的。@Dogbert不错的。现在有没有一种“快速而肮脏”的方法可以在不启动新项目并将其放入mix.exs的情况下将这个(或者通常是任何模块)导入到iex会话中?@ThomasBrowne不太可能。我要做的是:克隆benchee,cd
到其中,将代码复制到foo.exs
,然后运行mix-run-foo.exs
。另外25%的偏差建议不要把10%的差异看得太严重。您是如何进行实际的基准测试的?“它是内置在iex中的吗?”我用过托马斯布朗。它不是内置的。@Dogbert不错的。现在有没有一种“快速而肮脏”的方法可以在不启动新项目并将其放入mix.exs的情况下将这个(或者通常是任何模块)导入到iex会话中?@ThomasBrowne不太可能。我所做的是:克隆benchee,cd
到其中,将代码复制到foo.exs
,然后运行mix-run-foo.exs
。是的,我会在我的答案或@mudasobwa的答案上使用它,除非保存那些微秒是非常重要的。令人惊讶的是,我正在Arm架构的机器上运行它,这样可能会有所不同,但这至少比其他两个答案快2倍。因此,结合其美丽的简单性,您将获得胜利。(对于其他的答案,我非常感谢fold和reduce得到了很好的演示。)@ThomasBrowne很高兴听到这个消息,这无疑教会了我们如何始终对基准测试持保留态度!然而,浏览Elixir源代码发现,min_by
在内部只使用reduce
,因此我很困惑这比其他解决方案更快。出于好奇:在您的用例中,典型的映射和列表大小是什么?实际上,我的用例通常是相当少的列表(最多20个),但每个列表中的元素数量可能很大(在2000到3000之间)。我已经尝试了我的用例和@dogbert的地图生成器,它似乎创建了1000个小列表,在这两种情况下,你的似乎都更快。我的用例更像这样:map=Enum.into(Enum.map(1..10,fnx->{x,Enum.to_列表(1..2000+:rand.uniform(1000)))