Proposal: Map.filter_values and Map.filter_keys

77 views
Skip to first unread message

Filip Haglund

unread,
Jun 18, 2016, 12:27:07 PM6/18/16
to elixir-lang-core
I wish there was a Map.filter_values function that would filter on keys or values, but leave the other one intact. This seems like something that should be in the standard library.

Example implementation of a Map.filter_values that would filter a map based on its values, leaving the keys intact:

@doc """
Filter map based on its values, leaving the corresponding keys intact.
"""
@spec map_filter_values(Map.t, (any -> boolean)) :: Map.t
def map_filter_values(map, func) do
map |> Enum.filter(fn {_, val} -> func.(val) end) |> Map.new
end


A corresponding Map.filter_keys would also be nice.

Ben Wilson

unread,
Jun 18, 2016, 1:14:00 PM6/18/16
to elixir-lang-core
Your example implementation shows why this isn't really necessary. Moreover it doesn't compose well. Consider:

map
|> Enum.filter(fn {k, v} -> key_filter(k) && value_filter(v) end)
|> Map.new

vs

map
|> Map.filter_keys(key_filter)
|> Map.filter_values(value_filter)

In the latter it starts as a map, becomes a list, becomes a map again, then becomes a list, then becomes a map again.

There are a common set of operations which make sense for both lists, maps, streams, and other user made datastructures. Those common operations have been extracted into the Enum module based on the Enumerable protocol, and thats where they belong.

Filip Haglund

unread,
Jun 18, 2016, 4:19:05 PM6/18/16
to elixir-lang-core
Sorry, I was unclear. The example implementation was an example of how the function would work, not how it would be implemented. I was imagining an implementation that didn't build an intermediate list. Is building a new map each step too expensive for this to be considered composable? There's already Map.take/2 which is somewhat similar in functionality to filter_keys. 

Filip Haglund

unread,
Jun 18, 2016, 4:49:04 PM6/18/16
to elixir-lang-core
Here's another implementation, using Enum.reduce, without an intermediate list: 

def map_filter_values(map, func) do
Enum.reduce map, %{}, fn {key, val}, acc ->
if func.(val) do
Map.put(acc, key, val)
else
acc
end
end
end

On Saturday, June 18, 2016 at 6:27:07 PM UTC+2, Filip Haglund wrote:
Message has been deleted

Ben Wilson

unread,
Jun 18, 2016, 8:39:05 PM6/18/16
to elixir-lang-core
Well I tried to reply but my email was deleted.

Long story short, successive Map.put calls are actually inferior to walking intermediary lists and building a new map at the end. You can read more about it here: https://github.com/elixir-lang/elixir/pull/4415

You'll note that Map.take is actually built in just that way: https://github.com/elixir-lang/elixir/blob/master/lib/elixir/lib/map.ex#L330

Allen Madsen

unread,
Jun 18, 2016, 9:26:20 PM6/18/16
to elixir-l...@googlegroups.com
I have used the pattern this code wraps often and would use this
feature if it existed.
Allen Madsen
http://www.allenmadsen.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-lang-co...@googlegroups.com.
> To view this discussion on the web visit
> https://groups.google.com/d/msgid/elixir-lang-core/58c89485-0334-45ca-95f9-2a519060fc41%40googlegroups.com.
>
> For more options, visit https://groups.google.com/d/optout.

Ben Wilson

unread,
Jun 18, 2016, 10:53:05 PM6/18/16
to elixir-lang-core, allen.c...@gmail.com
Stepping back for a moment, there are a lot of functions in the Enum module that apply to maps. Are we to implement all of them in the Map module just to avoid having to call Map.new after our various Enum calls?

The way things work at the moment is that when we need to take one item out at a time we convert the map to a list. This is not only semantically sensible, it's also efficient and fast. Once we have that list we leave it as a list so that we can easily compose these operations. If we no longer want a list, we can build a new map. This is also the fastest way to go about this.

Filip Haglund

unread,
Jun 22, 2016, 2:53:39 PM6/22/16
to elixir-lang-core
If we're considering composability, this is a bad idea, yes. Adding it (and the likes) to Map would encourage people to use those for maps instead of Enum, which would be a noticable performance impact (a factor log n slower). 

I'd vote for not including this feature, because of the damage it might do by leading people away from Enum.


On Saturday, June 18, 2016 at 6:27:07 PM UTC+2, Filip Haglund wrote:
Reply all
Reply to author
Forward
0 new messages