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且没有更新值。
答案 0 :(得分:3)
我可以看到一些让你绊倒的事情:
Enum.each/2
会将一个函数应用于列表中的每个项目,但它不会以任何方式累积结果或修改原始列表。我上次使用Enum.each
时无法记住 - 它有它的位置,但它很少见。
这意味着您正在调用local[team]
的地方实际上并未更新任何值,因为该输出未传递给其他任何内容。它基本上只是将这些变化发送到以太网中。
解决方案:管道!我保证|>
管道操作员会改变你的生活。如果你来自OO背景,那么一开始有点令人难以置信,但坚持下去并且我保证你永远不会想要回去。思考的转变帮助我解决了这个问题,首先是尽可能少地使用匿名函数。它帮助我适应了不变性的概念,因为它迫使我思考每个函数实际需要的值,以及如何将这些值传递给每个函数。
在这里,我尝试使用更加以管道为中心的方法重写您的模块 - 希望它有所帮助。它在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.