[Proposal] Add Enum.key_by/3

35 views
Skip to first unread message

Sabiwara Yukichi

unread,
Jul 24, 2020, 10:34:27 PM7/24/20
to elixir-lang-core
Hi! I would like to propose introducing an `Enum.key_by/3` function (name to be discussed) that would return a map where:
- each key is the result of the first callback applied to an item
- the value is the result of the second callback applied to the item (identity by default)

It would basically be a copy of Enum.group_by/3 keeping only the last (or first?) value for each key instead of building a list.

Rationale:

While it could be achieved easily enough using Enum.map/2 and Map.new/1, or Enum.into/3, having a function for it could help making the code more explicit about the intent and therefore more readable. Like Enum.group_by/3, I think it is a fairly common operation and it might be a natural candidate for the standard library?

Example use case: from a list of Ecto records, create a map of records keyed by `id` for efficient lookups in future code:

User |> Repo.all() |> Enum.key_by(fn user -> user.id end)

If we want to build a map to lookup user names by their ids:
User |> Repo.all() |> Enum.key_by(fn user -> user.id end, fn user -> user.name end)


Further considerations:

The typical use cases I have in mind would rely on some kind of unique key, so I'm not sure what would be the best API-wise when dealing with duplicate keys and have no real opinion:
- keep the last value found for each key
- keep the first value found for each key
This behaviour could always be changed using `Enum.reverse/1` if needed.

What do you think?

I have a working branch for this here, happy to open a PR if you are interested: https://github.com/elixir-lang/elixir/compare/master...sabiwara:enum_key_by?expand=1

Zachary Daniel

unread,
Jul 24, 2020, 10:47:55 PM7/24/20
to elixir-lang-core
I like it, I write this function by hand all the time. I like the name, and I think that we should keep the first value found for each key.

Sabiwara Yukichi

unread,
Jul 24, 2020, 11:15:32 PM7/24/20
to elixir-lang-core
Thank you, I'm glad to see I'm not the only one finding myself needing this :)

> I think that we should keep the first value found for each key
I really wasn't sure about which one would be more consistent with other Elixir APIs, I actually also have a branch keeping the first occurrence https://github.com/elixir-lang/elixir/compare/master...sabiwara:enum_key_by_first?expand=1

José Valim

unread,
Jul 25, 2020, 3:08:07 AM7/25/20
to elixir-l...@googlegroups.com
Hi Sabiwara,

This is very close to Map.new/2 (and Enum.into/3):

User |> Repo.all() |> Map.new(fn user -> {user.id, user} end)

So my suggestion is to use Map.new/2 instead of adding a new function. :)

Thanks for the proposal!

--
You received this message because you are subscribed to the Google Groups "elixir-lang-core" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-co...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-core/b113e3c5-eb81-4484-a499-60700fb5dacfo%40googlegroups.com.

Zach Daniel

unread,
Jul 25, 2020, 3:34:42 AM7/25/20
to elixir-l...@googlegroups.com
TIL: I knew about Enum.into but not Map.new.

Sabiwara Yukichi

unread,
Jul 25, 2020, 6:33:22 AM7/25/20
to elixir-lang-core
> TIL: I knew about Enum.into but not Map.new.
Same here! :)

Thank you Jose, this suggestion solves the use cases I had in a very simple and natural way! I agree there is no need to add a new function.

Le samedi 25 juillet 2020 16:34:42 UTC+9, Zach Daniel a écrit :
TIL: I knew about Enum.into but not Map.new.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-l...@googlegroups.com.

--
You received this message because you are subscribed to the Google Groups "elixir-lang-core" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-l...@googlegroups.com.
Reply all
Reply to author
Forward
0 new messages