Elixir 中的不变性真的让我大吃一惊,使语言使用起来如此混乱。我需要迭代嵌套地图并根据迭代简单更新一些计数,但这Enum.reduce
对我来说很困难。说我有:
defmodule Predictor do
def past_matches() do
[
team1: %{team2: %{f: 0, a: 1}, team3: %{f: 1, a: 3}},
team2: %{team1: %{f: 3, a: 0}, team3: %{f: 2, a: 0}},
team3: %{team1: %{f: 1, a: 0}, team2: %{f: 0, a: 1}},
]
end
def init_indexes(matches) do
local = Enum.reduce matches, %{}, fn({team, _scores}, acc) ->
Map.put acc, team, %{f: 0, a: 0, n_games: 0}
end
Enum.each matches, fn({team, scores}) ->
Enum.each scores, fn({vteam, %{f: ff, a: aa}}) ->
%{f: fi, a: ai, n_games: ni} = local[team]
put_in(local[team], %{f: fi+ff, a: ai+aa, n_games: ni+1})
end
end
local
end
def run() do
local = past_matches() |> init_indexes()
end
end
我只需要local
总结f
,a
和n_games
。
local = %{
team1: %{f: 1, a: 4, n_games: 2}
...
}
在年底run()
明显的地图local
有全0和没有更新的价值。
有几件事我可以立即看到让你绊倒:
Enum.each/2
将对列表中的每个项目应用一个函数,但它不会以任何方式累积结果或修改原始列表。我不记得上次使用是Enum.each
什么时候了——它有它的位置,但非常罕见。
这意味着您调用的地方local[team]
实际上并没有更新任何值,因为该输出没有传递到其他任何地方。它本质上只是将这些更改发送到以太坊中。
解决方案:管道!我向你保证,|>
管道操作员会改变你的生活。如果您来自面向对象的背景,一开始有点令人难以置信,但坚持下去,我保证您永远不想回去。帮助我理解它的思维转变是一开始尽可能少地使用匿名函数。它帮助我适应了不变性的概念,因为它迫使我思考每个函数实际需要什么值,以及如何将这些值传递给每个函数。
这是我尝试使用更以管道为中心的方法重写您的模块 - 希望它有所帮助。当您在 IEx 中运行它时,它会产生预期的结果。如果您有任何疑问,我很乐意澄清任何问题。
defmodule Predictor do
@past_matches [
team1: %{
team2: %{f: 0, a: 1},
team3: %{f: 1, a: 3}
},
team2: %{
team1: %{f: 3, a: 0},
team3: %{f: 2, a: 0}
},
team3: %{
team1: %{f: 1, a: 0},
team2: %{f: 0, a: 1}
}
]
# see note 1
@baseline %{f: 0, a: 0, n_games: 0}
def run(past_matches \\ @past_matches) do
past_matches
|> Enum.map(&build_histories/1)
|> Enum.into(%{})
# see note 2
end
def build_histories({team, scores}) do
history = Enum.reduce(scores, @baseline, &build_history/2)
{team, history}
end
def build_history({_vteam, vresults}, acc) do
# see note 3
%{acc | f: acc.f + vresults.f,
a: acc.a + vresults.a,
n_games: acc.n_games + 1}
end
end
(1) since the baseline is the same for every team, you can
set it as a module attribute -- basically like setting a global
(immutable) variable that you can use as a starting point for a new
value. Another option would be to create a %BaseLine{} struct that
has default values.
(2) you could also use `Enum.reduce/2` here instead, but this does
effectively the same thing -- the output of the `Enum.map/1`
call is a list of {atom, _val} which is interpreted as a Keyword
list; calling `Enum.into(%{}) turns a Keyword list into a map
(and vice versa with `Enum.into([])`).
(3) NB: %{map | updated_key: updated_val} only works on maps or
structs where the key to be updated already exists -- it'll throw
an error if the key isn't found on the original map.
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句