Iago Díaz
Iago Díaz

Reputation: 368

Best way to update a list of structs

Let's say that I have a list of structs, with an id as one of its keys:

users = [
  %User{id: 1, attempts: 5},
  %User{id: 2, attempts: 12},
  %User{id: 3, attempts: 2}
]

What would the best way (the most elixir way) to update the number of attempts to 99 of the user with id == 2 to get a new users list:

updated_users = [
  %User{id: 1, attempts: 5},
  %User{id: 2, attempts: 99},
  %User{id: 3, attempts: 2}
]

Thanks for helping!

Upvotes: 6

Views: 1438

Answers (3)

Aleksei Matiushkin
Aleksei Matiushkin

Reputation: 121010

Arguably, most elixirish approach would be to use Access behaviour and Kernel.put_in/3:

put_in users,
  [Access.filter(&match?(%User{id: 2}, &1)), 
   Access.key(:attempts)],
  99
#⇒ [
#  %User{attempts: 5, id: 1},
#  %User{attempts: 99, id: 2},
#  %User{attempts: 2, id: 3}
#]

This might be easily extended to update multiple elements by changing the filter function.

Upvotes: 5

Koziołek
Koziołek

Reputation: 2874

Enum.map(users, fn
  %User{id: 2} = user -> %User{user | attempts: 99}
  user -> user
end)

You can encapsulate it into some module and give it nice name :)

Upvotes: 6

Adam Millerchip
Adam Millerchip

Reputation: 23137

If you are doing many updates, it's probably best to build a map, so that you don't have to iterate the list for each update.

[
  %User{attempts: 5, id: 1},
  %User{attempts: 12, id: 2},
  %User{attempts: 2, id: 3}
]
|> Map.new(fn user -> {user.id, user} end)
|> Map.update!(2, &%User{&1 | attempts: 99})
|> Map.values()

You can do as many Map.update!/3 calls as you need, to update different ids.

Upvotes: 1

Related Questions