LoginSignup
5
2
記事投稿キャンペーン 「2024年!初アウトプットをしよう」

【TIPS】マップキーワードリストの更新でEnum回さない方法

Last updated at Posted at 2024-01-01

この記事は、Elixir Advent Calendar 2023 シリーズ10 の19日目です


【本コラムは、6分で読め、6分で試せます】

piacere です、ご覧いただいてありがとございます :bow:

下記のようなマップキーワードリストは、同列の複数データを扱う際にとても便利です

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)で解説されているのですが、私は長年、読み飛ばしてて、知りませんでした :sweat:

image.png

image.png

5
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
2