この記事は、Elixir Advent Calendar 2023 シリーズ10 の19日目です
【本コラムは、6分で読め、6分で試せます】
piacere です、ご覧いただいてありがとございます
下記のようなマップキーワードリストは、同列の複数データを扱う際にとても便利です
users = [
id000: %{name: "hoge", weight: 60},
id001: %{name: "foo", weight: nil},
id002: %{name: "fuga", weight: 49}
]
しかし、ネストした構造のため、中身の更新が面倒な印象があります
更新後
[
+ id_000: %{name: "piacere", weight: 60},
+ id_001: %{name: "fuchan", weight: 55},
+ id_002: %{name: "fuga", weight: 49}
]
そこを様々な方法で書いてみたいと思います
Enum.map + if + Map.put
users
|> Enum.map(fn {k, v} -> if k == :id000, do: {k, Map.put(v, :name, "piacere")}, else: {k, v} end)
|> Enum.map(fn {k, v} -> if k == :id001, do: {k, v |> Map.put(:name, "fuchan") |> Map.put(:weight, 55)}, else: {k, v} end)
for + if + Map.put
forはパイプが使えなくて、一時変数が必要なため面倒です
for {k, v} <- users do
{k2, v2} = if k == :id000, do: {k, Map.put(v, :name, "piacere")}, else: {k, v}
if k2 == :id001, do: {k2, v2 |> Map.put(:name, "fuchan") |> Map.put(:weight, 55)}, else: {k2, v2}
end
ちなみに、こんな風に書くこともできます
({k, v} <- users)
|> for do
{k2, v2} = if k == :id000, do: {k, Map.put(v, :name, "piacere")}, else: {k, v}
if k2 == :id001, do: {k2, v2 |> Map.put(:name, "fuchan") |> Map.put(:weight, 55)}, else: {k2, v2}
end
for/reduce + if + Map.put
forもreduceを使うと、一時変数を消せます
ポイントは、[(~)]
のように、連結するリストの中の式をカッコで囲むことです
for {k, v} <- users, reduce: [] do
acc -> acc ++ [(if k == :id000, do: {k, Map.put(v, :name, "piacere")}, else: {k, v})]
acc ++ [(if k == :id001, do: {k, v |> Map.put(:name, "fuchan") |> Map.put(:weight, 55)}, else: {k, v})]
end
List.replace_at
書き換える対象のインデックスと全データを指定可能であれば、こんな書き方もできます(が、検索もできなければ計算もできないので実践的では無いし、本サンプルで言えば丸ごと書き換えた方が早いw)
users
|> List.replace_at(0, {:id000, %{name: "piacere", weight: 60}})
|> List.replace_at(1, {:id001, %{name: "fuchan", weight: 55}})
put_in、update_in
一時変数は使うものの、多分これが最強です
users2 = put_in(users[:id000].name, "piacere")
put_in(users2[:id001], %{name: "fuchan", weight: 55})
もっと深いネストでも大丈夫です(これをEnumで処理すると凄まじく大変そうです)
user_details = [
id000: %{name: "hoge", properties: %{age: 49, weight: 60}},
id001: %{name: "foo", properties: %{age: 16, weight: nil}},
id002: %{name: "fuga", properties: %{age: 27, weight: 33}}
]
put_in(user_details[:id001].properties.weight, 55)
更新後
[
id000: %{name: "hoge", properties: %{age: 49, weight: 60}},
+ id001: %{name: "foo", properties: %{age: 16, weight: 55}},
id002: %{name: "fuga", properties: %{age: 27, weight: 33}}
]
update_in
を使うと、値の更新に関数も使えます
update_in(users[:id002].name, & &1 <> "-kun")
更新後
[
id000: %{name: "hoge", weight: 60},
id001: %{name: "foo", weight: nil},
+ id002: %{name: "fuga-kun", weight: 49}
]
このテクニックは、書籍「プログラミングElixir」 のP90(第一版だとP84)で解説されているのですが、私は長年、読み飛ばしてて、知りませんでした