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

この記事は、[Elixir Advent Calendar 2023 シリーズ10](https://qiita.com/advent-calendar/2023/elixir) の19日目です

---

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

[piacere](https://twitter.com/piacere_ex) です、ご覧いただいてありがとございます :bow:

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

```diff_elixir
users = [
  id000: %{name: "hoge", weight: 60}, 
  id001: %{name: "foo",  weight: nil}, 
  id002: %{name: "fuga", weight: 49}
]
```

しかし、ネストした構造のため、中身の更新が面倒な印象があります

```diff_elixir:更新後
[
+ id_000: %{name: "piacere", weight: 60}, 
+ id_001: %{name: "fuchan",  weight: 55}, 
+ id_002: %{name: "fuga", weight: 49}
]
```

そこを様々な方法で書いてみたいと思います

# Enum.map ＋ if ＋ Map.put

```diff_elixir
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はパイプが使えなくて、一時変数が必要なため面倒です

```diff_elixir
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
```

ちなみに、こんな風に書くこともできます

```diff_elixir
({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を使うと、一時変数を消せます

ポイントは、`[(～)]` のように、連結するリストの中の式をカッコで囲むことです

```diff_elixir
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）

```diff_elixir
users
|> List.replace_at(0, {:id000, %{name: "piacere", weight: 60}}) 
|> List.replace_at(1, {:id001, %{name: "fuchan", weight: 55}})
````

# put_in、update_in

一時変数は使うものの、多分これが最強です

```diff_elixir
users2 = put_in(users[:id000].name, "piacere")
put_in(users2[:id001], %{name: "fuchan", weight: 55}) 
```

もっと深いネストでも大丈夫です（これをEnumで処理すると凄まじく大変そうです）

```diff_elixir
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) 
```

```diff_elixir:更新後
[
  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` を使うと、値の更新に関数も使えます

```diff_elixir
 update_in(users[:id002].name, & &1 <> "-kun") 
```

```diff_elixir:更新後
[
  id000: %{name: "hoge", weight: 60},
  id001: %{name: "foo", weight: nil},
+ id002: %{name: "fuga-kun", weight: 49}
]
```

このテクニックは、[**書籍「プログラミングElixir」**](https://www.amazon.co.jp/exec/obidos/ASIN/4274226379/onelinebiz-22) のP90（第一版だとP84）で解説されているのですが、私は長年、読み飛ばしてて、知りませんでした :sweat: 

[![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/73019c11-dc0d-9900-e06b-1d188240a4ae.png)](https://www.amazon.co.jp/exec/obidos/ASIN/4274226379/onelinebiz-22)

[![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/a4e2076c-8106-51d1-ab0a-59f63dc59a2e.png)](https://www.amazon.co.jp/exec/obidos/ASIN/4274219151/onelinebiz-22)