如何使用另一个地图列表中的值更新地图列表?
How do I update a list of maps with the values from another list of maps?
我有这样的地图列表:
primary_list = [
%{"id" => "cue", "is_idle" => true, "velocity_x" => 0.0, "velocity_z" => 0.0, "x" => -15.0, "z" => 0.0},
%{"id" => "ball_1", "is_idle" => true, "velocity_x" => 0.0, "velocity_z" => 0.0, "x" => 15.0, "z" => 0.0},
%{"id" => "ball_2", "is_idle" => true, "velocity_x" => 0.0, "velocity_z" => 0.0, "x" => 17.0, "z" => 1.1},
%{"id" => "ball_3", "is_idle" => true, "velocity_x" => 0.0, "velocity_z" => 0.0, "x" => 17.0, "z" => -1.1}
]
还有一个,像这样:
new_list = [
%{"ball_1" => %{x: 14.684729230724962, y: 0.0, z: 0.0},
"cue" => %{x: 16.68472923072496, y: 0.0, z: 0.0}},
%{"ball_2" => %{x: 17.68472923072496, y: 0.0, z: 1.1},
"cue" => %{x: 15.684729230724962, y: 0.0, z: 1.1}},
%{"ball_3" => %{x: 17.68472923072496, y: 0.0, z: -1.1},
"cue" => %{x: 15.684729230724962, y: 0.0, z: -1.1}},
%{"ball_2" => %{x: 17.68472923072496, y: 0.0, z: 0.0},
"cue" => %{x: 15.684729230724962, y: 0.0, z: 0.0}},
%{"ball_3" => %{x: 17.68472923072496, y: 0.0, z: 0.0},
"cue" => %{x: 15.684729230724962, y: 0.0, z: 0.0}}
]
如何迭代第二张地图,并根据 ID 将对 x、y 和 z 值的更改应用到第一张地图,同时保持第一张地图的格式以使最终结果看起来像像这样:
new_primary_list = [
%{"id" => "cue", "is_idle" => true, "velocity_x" => 0.0, "velocity_z" => 0.0, "x" => 15.684729230724962, "z" => 0.0},
%{"id" => "ball_1", "is_idle" => true, "velocity_x" => 0.0, "velocity_z" => 0.0, "x" => 14.684729230724962, "z" => 0.0},
%{"id" => "ball_2", "is_idle" => true, "velocity_x" => 0.0, "velocity_z" => 0.0, "x" => 17.68472923072496, "z" => 0.0},
%{"id" => "ball_3", "is_idle" => true, "velocity_x" => 0.0, "velocity_z" => 0.0, "x" => 17.68472923072496, "z" => 0.0}
]
有些项目是重复的,但最后的值应该是最后更新的(比如ball_2
和ball_3
的情况)。我试过这个:
new_result = Enum.map(result, fn res ->
Enum.map(collision_map, fn cmap ->
Enum.map(cmap, fn {k, v} ->
if res["id"] == k do
res
|> Map.put("x", v["x"])
|> Map.put("z", v["z"])
end
end)
end)
end)
但我最终得到的只是一堆乱七八糟的列表,其中包含很多 nil
个值:
[
[nil, nil], [nil, nil],
[%{"id" => "ball_3", "is_idle" => true, "velocity_x" => 0.0, "velocity_z" => 0.0, "x" => nil, "z" => nil}, nil],
[nil, nil],
[%{"id" => "ball_3", "is_idle" => true, "velocity_x" => 0.0, "velocity_z" => 0.0, "x" => nil, "z" => nil}, nil]
]
还有什么我可以尝试的吗?
展平 new_list
使其易于迭代,然后逐步更新 primary_list
。
请注意,这是非常低效的,您最好重新考虑使用 Map
s(例如,使用 id
作为键。)
new_list
|> Enum.flat_map(& &1)
|> Enum.reduce(primary_list, fn {id, values}, acc ->
update_in(
acc,
[Access.filter(& &1["id"] == id)],
&Map.merge(&1, values)
)
end)
您可能需要将 new_list
的键转换为字符串或使其工作。
结果:
我更新了一点 Aleksei Matiushkin 的回答:
primary_list = Map.new(primary_list, & {&1["id"], &1}) # convert primary_list into a map
new_list
|> Enum.flat_map(& &1)
|> Enum.reduce(primary_list, fn {k, v}, acc -> # iterate over new_list and pass primary_list as accumulator
Map.update!(
acc,
k,
& Map.merge(&1, %{"x" => v.x, "y" => v.y, "z" => v.z}) # update primary_list values with new_list
)
end)
|> Map.values() # covert result back into a list
正如我在您的 中提到的,列表不适合随机访问。因为您想通过 ID 更新特定元素,所以最好将您的状态存储在地图中,因为地图就是为此而设计的。
如果您清理数据以使其更易于使用,则实施会非常简单:
def update_state do
{primary_map, new_list} = sanitize()
Enum.reduce(new_list, primary_map, fn %{ball: ball, cue: cue}, primary_map ->
primary_map = put_in(primary_map["cue"]["x"], cue.x)
primary_map = put_in(primary_map["cue"]["y"], cue.y)
primary_map = put_in(primary_map["cue"]["z"], cue.z)
primary_map = put_in(primary_map[ball.id]["x"], ball.pos.x)
primary_map = put_in(primary_map[ball.id]["y"], ball.pos.y)
put_in(primary_map[ball.id]["z"], ball.pos.z)
end)
|> Map.values()
end
输出:
[
%{
"id" => "ball_1",
"is_idle" => true,
"velocity_x" => 0.0,
"velocity_z" => 0.0,
"x" => 14.684729230724962,
"y" => 0.0,
"z" => 0.0
},
%{
"id" => "ball_2",
"is_idle" => true,
"velocity_x" => 0.0,
"velocity_z" => 0.0,
"x" => 17.68472923072496,
"y" => 0.0,
"z" => 1.1
},
%{
"id" => "ball_3",
"is_idle" => true,
"velocity_x" => 0.0,
"velocity_z" => 0.0,
"x" => 17.68472923072496,
"y" => 0.0,
"z" => -1.1
},
%{
"id" => "cue",
"is_idle" => true,
"velocity_x" => 0.0,
"velocity_z" => 0.0,
"x" => 15.684729230724962,
"y" => 0.0,
"z" => 0.0
}
]
在sanitize
我做了两件事:
- 将状态转换为地图。
Map.new(primary_list, fn item -> {item["id"], item} end)
这给了我们:
%{
"ball_1" => %{
"id" => "ball_1",
"is_idle" => true,
"velocity_x" => 0.0,
"velocity_z" => 0.0,
"x" => 15.0,
"z" => 0.0
}, ...
}
这更容易使用,允许我们使用 put_in
和上面的球 ID。
- 将
new_list
转换为具有一致的密钥。您的球键是 ball_1
、ball_2
等,但这使得很难从循环中以编程方式访问它们。拥有一致的 ID 会更好:ball
.
for item <- new_list do
[id] = Map.keys(item) -- ["cue"]
%{
ball: %{id: id, pos: item[id]},
cue: item["cue"]
}
end
这为我们提供了这种格式的 new_list
,使我们能够为每个项目查找 ball
:
[
%{
ball: %{id: "ball_1", pos: %{x: 14.684729230724962, y: 0.0, z: 0.0}},
cue: %{x: 16.68472923072496, y: 0.0, z: 0.0}
}, ...
]
这是完整的 sanitize
函数:
def sanitize do
primary_list = primary_list()
primary_map = Map.new(primary_list, fn item -> {item["id"], item} end)
new_list = new_list()
new_list =
for item <- new_list do
[id] = Map.keys(item) -- ["cue"]
%{
ball: %{id: id, pos: item[id]},
cue: item["cue"]
}
end
{primary_map, new_list}
end
我有这样的地图列表:
primary_list = [
%{"id" => "cue", "is_idle" => true, "velocity_x" => 0.0, "velocity_z" => 0.0, "x" => -15.0, "z" => 0.0},
%{"id" => "ball_1", "is_idle" => true, "velocity_x" => 0.0, "velocity_z" => 0.0, "x" => 15.0, "z" => 0.0},
%{"id" => "ball_2", "is_idle" => true, "velocity_x" => 0.0, "velocity_z" => 0.0, "x" => 17.0, "z" => 1.1},
%{"id" => "ball_3", "is_idle" => true, "velocity_x" => 0.0, "velocity_z" => 0.0, "x" => 17.0, "z" => -1.1}
]
还有一个,像这样:
new_list = [
%{"ball_1" => %{x: 14.684729230724962, y: 0.0, z: 0.0},
"cue" => %{x: 16.68472923072496, y: 0.0, z: 0.0}},
%{"ball_2" => %{x: 17.68472923072496, y: 0.0, z: 1.1},
"cue" => %{x: 15.684729230724962, y: 0.0, z: 1.1}},
%{"ball_3" => %{x: 17.68472923072496, y: 0.0, z: -1.1},
"cue" => %{x: 15.684729230724962, y: 0.0, z: -1.1}},
%{"ball_2" => %{x: 17.68472923072496, y: 0.0, z: 0.0},
"cue" => %{x: 15.684729230724962, y: 0.0, z: 0.0}},
%{"ball_3" => %{x: 17.68472923072496, y: 0.0, z: 0.0},
"cue" => %{x: 15.684729230724962, y: 0.0, z: 0.0}}
]
如何迭代第二张地图,并根据 ID 将对 x、y 和 z 值的更改应用到第一张地图,同时保持第一张地图的格式以使最终结果看起来像像这样:
new_primary_list = [
%{"id" => "cue", "is_idle" => true, "velocity_x" => 0.0, "velocity_z" => 0.0, "x" => 15.684729230724962, "z" => 0.0},
%{"id" => "ball_1", "is_idle" => true, "velocity_x" => 0.0, "velocity_z" => 0.0, "x" => 14.684729230724962, "z" => 0.0},
%{"id" => "ball_2", "is_idle" => true, "velocity_x" => 0.0, "velocity_z" => 0.0, "x" => 17.68472923072496, "z" => 0.0},
%{"id" => "ball_3", "is_idle" => true, "velocity_x" => 0.0, "velocity_z" => 0.0, "x" => 17.68472923072496, "z" => 0.0}
]
有些项目是重复的,但最后的值应该是最后更新的(比如ball_2
和ball_3
的情况)。我试过这个:
new_result = Enum.map(result, fn res ->
Enum.map(collision_map, fn cmap ->
Enum.map(cmap, fn {k, v} ->
if res["id"] == k do
res
|> Map.put("x", v["x"])
|> Map.put("z", v["z"])
end
end)
end)
end)
但我最终得到的只是一堆乱七八糟的列表,其中包含很多 nil
个值:
[
[nil, nil], [nil, nil],
[%{"id" => "ball_3", "is_idle" => true, "velocity_x" => 0.0, "velocity_z" => 0.0, "x" => nil, "z" => nil}, nil],
[nil, nil],
[%{"id" => "ball_3", "is_idle" => true, "velocity_x" => 0.0, "velocity_z" => 0.0, "x" => nil, "z" => nil}, nil]
]
还有什么我可以尝试的吗?
展平 new_list
使其易于迭代,然后逐步更新 primary_list
。
请注意,这是非常低效的,您最好重新考虑使用 Map
s(例如,使用 id
作为键。)
new_list
|> Enum.flat_map(& &1)
|> Enum.reduce(primary_list, fn {id, values}, acc ->
update_in(
acc,
[Access.filter(& &1["id"] == id)],
&Map.merge(&1, values)
)
end)
您可能需要将 new_list
的键转换为字符串或使其工作。
结果:
我更新了一点 Aleksei Matiushkin 的回答:
primary_list = Map.new(primary_list, & {&1["id"], &1}) # convert primary_list into a map
new_list
|> Enum.flat_map(& &1)
|> Enum.reduce(primary_list, fn {k, v}, acc -> # iterate over new_list and pass primary_list as accumulator
Map.update!(
acc,
k,
& Map.merge(&1, %{"x" => v.x, "y" => v.y, "z" => v.z}) # update primary_list values with new_list
)
end)
|> Map.values() # covert result back into a list
正如我在您的
如果您清理数据以使其更易于使用,则实施会非常简单:
def update_state do
{primary_map, new_list} = sanitize()
Enum.reduce(new_list, primary_map, fn %{ball: ball, cue: cue}, primary_map ->
primary_map = put_in(primary_map["cue"]["x"], cue.x)
primary_map = put_in(primary_map["cue"]["y"], cue.y)
primary_map = put_in(primary_map["cue"]["z"], cue.z)
primary_map = put_in(primary_map[ball.id]["x"], ball.pos.x)
primary_map = put_in(primary_map[ball.id]["y"], ball.pos.y)
put_in(primary_map[ball.id]["z"], ball.pos.z)
end)
|> Map.values()
end
输出:
[
%{
"id" => "ball_1",
"is_idle" => true,
"velocity_x" => 0.0,
"velocity_z" => 0.0,
"x" => 14.684729230724962,
"y" => 0.0,
"z" => 0.0
},
%{
"id" => "ball_2",
"is_idle" => true,
"velocity_x" => 0.0,
"velocity_z" => 0.0,
"x" => 17.68472923072496,
"y" => 0.0,
"z" => 1.1
},
%{
"id" => "ball_3",
"is_idle" => true,
"velocity_x" => 0.0,
"velocity_z" => 0.0,
"x" => 17.68472923072496,
"y" => 0.0,
"z" => -1.1
},
%{
"id" => "cue",
"is_idle" => true,
"velocity_x" => 0.0,
"velocity_z" => 0.0,
"x" => 15.684729230724962,
"y" => 0.0,
"z" => 0.0
}
]
在sanitize
我做了两件事:
- 将状态转换为地图。
Map.new(primary_list, fn item -> {item["id"], item} end)
这给了我们:
%{
"ball_1" => %{
"id" => "ball_1",
"is_idle" => true,
"velocity_x" => 0.0,
"velocity_z" => 0.0,
"x" => 15.0,
"z" => 0.0
}, ...
}
这更容易使用,允许我们使用 put_in
和上面的球 ID。
- 将
new_list
转换为具有一致的密钥。您的球键是ball_1
、ball_2
等,但这使得很难从循环中以编程方式访问它们。拥有一致的 ID 会更好:ball
.
for item <- new_list do
[id] = Map.keys(item) -- ["cue"]
%{
ball: %{id: id, pos: item[id]},
cue: item["cue"]
}
end
这为我们提供了这种格式的 new_list
,使我们能够为每个项目查找 ball
:
[
%{
ball: %{id: "ball_1", pos: %{x: 14.684729230724962, y: 0.0, z: 0.0}},
cue: %{x: 16.68472923072496, y: 0.0, z: 0.0}
}, ...
]
这是完整的 sanitize
函数:
def sanitize do
primary_list = primary_list()
primary_map = Map.new(primary_list, fn item -> {item["id"], item} end)
new_list = new_list()
new_list =
for item <- new_list do
[id] = Map.keys(item) -- ["cue"]
%{
ball: %{id: id, pos: item[id]},
cue: item["cue"]
}
end
{primary_map, new_list}
end