Elixir 基于另一个嵌套贴图迭代更新贴图
《长生不老药》中的不变性真的让我大吃一惊,让语言使用起来如此混乱。我需要迭代一个嵌套映射,并根据迭代简单地更新一些计数,但是Elixir 基于另一个嵌套贴图迭代更新贴图,elixir,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: %{
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_游戏进行求和
local = %{
team1: %{f: 1, a: 4, n_games: 2}
...
}
在run()
的末尾,显然地图local
都是0,没有更新的值。我可以看到一些让你感到困惑的事情:
Enum.each/2
将对列表中的每个项目应用一个函数,但它不会累积结果或以任何方式修改原始列表。我记不起上次使用枚举的时间了。每个枚举都有自己的位置,但非常罕见
- 这意味着,您调用
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.
有几件事我可以从球棒上看到,让你绊倒了:
Enum.each/2
将对列表中的每个项目应用一个函数,但它不会累积结果或以任何方式修改原始列表。我记不起上次使用枚举的时间了。每个枚举都有自己的位置,但非常罕见
- 这意味着,您调用
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.
谢谢,这很有帮助!谢谢,这很有帮助!