使用多个键更新嵌套映射

时间:2016-09-12 05:18:55

标签: elixir

假设我们有这样的地图:

state = %{
      data: %{
        "default" => %{
          "someKeyword" => %{updated: 2}},

          "baila" => %{
            "someKeyword" => %{updated: 2}},
            "morena" => %{updated: 2}
        }
      }

在这个例子中,只有2个“父键”要检查 - >实际应用中的defaultbaila会有100s +

现在,当我获得更新后的关键字someKeyword的事件时,如何检查所有父键并有条件地更新是否找到了密钥,并且新密钥的某个值高于旧密钥的值。

类似的东西:

newKeyword = %{"someKeyword" => %{updated: 3, otherdata: 4}}
newKeywordkey = List.first(Map.keys(newKeyword))

keys = Map.keys(state.data)

Enum.each(keys, fn(x) ->
  new_state = case Kernel.get_and_update_in(x, [newKeywordkey], fn(y) ->
    if y.updated < newKeyword[newKeywordkey] do
         ....
     end
    {y, newKeyword}
  end)
   do
    {l, b} -> b
  end
end)

上面的例子很糟糕,不起作用,因为它似乎是一个混乱的解决方案,我在尴尬的长时间后放弃了:)

是否有更简单的方法来循环/检查上面的嵌套映射并在匹配时有条件地更新?

最终结果应为:

newKeyword = %{"someKeyword" => %{updated: 3, otherdata: 4}}
....
state = %{
      data: %{
        "default" => %{
          "someKeyword" => %{updated: 3, otherdata: 4}},

          "baila" => %{
            "someKeyword" => %{updated: 3, otherdata: 4}},
            "morena" => %{updated: 2}
        }
      }

3 个答案:

答案 0 :(得分:3)

我发现了一种更好,更简单的方法,请参阅here

Kernel.put_in/3

my_map = %{
  foo: %{
    bar: %{
      baz: "my value"
    }
  }
}

put_in(my_map, [:foo, :bar, :baz], "new value")

答案 1 :(得分:2)

以下是使用Enum.reduce/3Kernel.put_in/3的一种方式:

state = %{
  data: %{
    "default" => %{
      "someKeyword" => %{updated: 2}
     },
     "baila" => %{
       "morena" => %{updated: 2}
     }
  }
}

new_keyword = %{"someKeyword" => %{updated: 3, otherdata: 4}}
[{new_key, new_value}] = Map.to_list(new_keyword)

new_state = Enum.reduce(Map.keys(state.data), state, fn key, state ->
  old_value = state.data[key][new_key]
  if old_value == nil || old_value && new_value.updated > old_value.updated do
    put_in(state, [:data, key, new_key], new_value)
  else
    state
  end
end)

IO.inspect new_state

输出:

%{data: %{"baila" => %{"morena" => %{updated: 2},
      "someKeyword" => %{otherdata: 4, updated: 3}},
    "default" => %{"someKeyword" => %{otherdata: 4, updated: 3}}}}

答案 2 :(得分:2)

虽然@Dogbert的回答肯定是正确的(尽管限制它适用于一个键值对),但实现目标的方法更简单

new_data = %{"someKeyword" => %{updated: 3, otherdata: 4}}
%{data: state.data 
  |> Enum.map(fn {k, v} -> 
       {k, 
        (if v["someKeyword"] == nil ||
            v["someKeyword"][:updated] == nil ||
            v["someKeyword"][:updated] < new_data[:updated],
         do: v |> Map.merge(new_keyword), else: v)
       }
     end)
  |> Enum.into(%{})}

#⇒ %{data: %{"baila" => %{"morena" => %{updated: 2},
#       "someKeyword" => %{otherdata: 4, updated: 3}},
#     "default" => %{"someKeyword" => %{otherdata: 4, updated: 3}}}}

以上内容适用于合并任何新数据地图。

感谢@Dogbert指出此处需要updated的明确条件。