【问题标题】:Update Map based on another nested Map iteration基于另一个嵌套 Map 迭代更新 Map
【发布时间】:2017-06-05 08:37:41
【问题描述】:

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

我需要 localf,an_games 相加。

local = %{
    team1: %{f: 1, a: 4, n_games: 2}
    ...
}

run() 的末尾显然 local 的地图全为 0 且没有更新值。

【问题讨论】:

    标签: elixir


    【解决方案1】:

    有几件事我可以立即看到让你绊倒:

    • 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.
    

    【讨论】:

    • 谢谢,这很有帮助!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-08-23
    • 1970-01-01
    • 1970-01-01
    • 2018-05-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多