TL;DR - pull request is welcome.

My initial take was that the performance benefits are not significant but
then, after carefully checking the Enum API, I realized that my
slippery-slope argument does not hold: there aren't any other functions, as
far as I can tell, that would make sense to be ported to Map. Therefore, I
would say the small performance improvement and the API completeness
arguments are strong enough to move forward, so please do send a pull
request for Map.split_with/2 and Keyword.split_with/2.

On Mon, Dec 13, 2021 at 20:38 Chris Miller <camiller...@gmail.com> wrote:

> Of course some numbers would be great to look at here - here is a
> benchmark for an implementation of Map.split_with/2 comparing the results
> agains a few implementations using the current standard library
>
> Code
> ```elixir
> defmodule SplitWith do
>   def test do
>     map = Map.new(0..1000, fn n -> {n, n} end)
>     predicate = fn {x, _} -> rem(x, 2) == 0 end
>
>     Benchee.run(%{
>       "split_with" => fn -> split_with(map, predicate) end,
>       "enum_split_with" => fn -> enum_split_with(map, predicate) end,
>       "enum_reduce" => fn -> enum_reduce(map, predicate) end,
>       "map_filter_reject" => fn -> map_filter_reject(map, predicate) end
>     })
>   end
>
>   def split_with(map, fun) when is_map(map) and is_function(fun, 1) do
>     iter = :maps.iterator(map)
>     next = :maps.next(iter)
>     {while_true, while_false} = do_split_with({[], []}, next, fun)
>     {:maps.from_list(while_true), :maps.from_list(while_false)}
>   end
>
>   defp do_split_with(acc, :none, _fun), do: acc
>
>   defp do_split_with({while_true, while_false}, {key, value, iter}, fun) do
>     if fun.({key, value}) do
>       do_split_with({[{key, value} | while_true], while_false},
> :maps.next(iter), fun)
>     else
>       do_split_with({while_true, [{key, value} | while_false]},
> :maps.next(iter), fun)
>     end
>   end
>
>   def map_filter_reject(map, predicate) do
>     {Map.filter(map, &predicate.(&1)), Map.filter(map, fn pair -> not
> predicate.(pair) end)}
>   end
>
>   def enum_reduce(map, predicate) do
>     Enum.reduce(map, {%{}, %{}}, fn {key, value} = pair, {while_true,
> while_false} ->
>       if predicate.(pair) do
>         {Map.put(while_true, key, value), while_false}
>       else
>         {while_true, Map.put(while_false, key, value)}
>       end
>     end)
>   end
>
>   def enum_split_with(map, predicate) do
>     {while_true, while_false} = Enum.split_with(map, &predicate.(&1))
>     {Map.new(while_true), Map.new(while_false)}
>   end
> end
> ```
>
> Benchee results
> ```
> iex(1)> SplitWith.test
> Operating System: macOS
> CPU Information: Intel(R) Core(TM) i9-9980HK CPU @ 2.40GHz
> Number of Available Cores: 16
> Available memory: 32 GB
> Elixir 1.13.0
> Erlang 24.0.5
>
> Benchmark suite executing with the following configuration:
> warmup: 2 s
> time: 5 s
> memory time: 0 ns
> parallel: 1
> inputs: none specified
> Estimated total run time: 28 s
>
> Benchmarking enum_reduce...
> Benchmarking enum_split_with...
> Benchmarking map_filter_reject...
> Benchmarking split_with...
>
> Name                        ips        average  deviation         median
>       99th %
> split_with               5.73 K      174.41 μs    ±10.32%         170 μs
>       243 μs
> enum_split_with          5.06 K      197.47 μs    ±12.64%         191 μs
>       291 μs
> enum_reduce              4.52 K      221.10 μs    ±22.96%         207 μs
>       435 μs
> map_filter_reject        4.52 K      221.15 μs    ±11.35%         215 μs
>       313 μs
>
> Comparison:
> split_with               5.73 K
> enum_split_with          5.06 K - 1.13x slower +23.06 μs
> enum_reduce              4.52 K - 1.27x slower +46.69 μs
> map_filter_reject        4.52 K - 1.27x slower +46.73 μs
> ```
>
> So we do get some modest gains with the new implementation.  Let me know
> what your thoughts are on moving forward!
>
> And as always - really appreciate your work!
> On Monday, December 13, 2021 at 12:51:30 PM UTC-6 José Valim wrote:
>
>> Hi Chris,
>>
>> Thanks for the proposal.
>>
>> I would like to first see benchmarks that show a Map implementation can
>> be considerably more efficient. Otherwise, if it is about saving a couple
>> Map.new calls, then I would rather not add it, as it will move to copying
>> many more functions from Enum to Map.
>>
>> On Mon, Dec 13, 2021 at 7:42 PM Chris Miller <camil...@gmail.com> wrote:
>>
>>> Is there any interest in adding a `Map.split_with/2` that would take a
>>> function of `{key :: any(), value :: any()} -> boolean` and returns
>>> `{map_where_true :: map(), map_where_false :: map()}`?
>>>
>>> I know this functionality can be created easily with the functionality
>>> thats already exposed, but it seems like it might be a nice addition and
>>> would add greater parity between Enum and Map - it could also be added to
>>> Keyword, even thought the distinction between Keyword.split_with and
>>> Enum.split_with would be nominal.
>>>
>>>
>>> --
>>> 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/0a7b4be9-ccb9-4c6a-b482-96379a6a9a18n%40googlegroups.com
>>> <https://groups.google.com/d/msgid/elixir-lang-core/0a7b4be9-ccb9-4c6a-b482-96379a6a9a18n%40googlegroups.com?utm_medium=email&utm_source=footer>
>>> .
>>>
>> --
> 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-core+unsubscr...@googlegroups.com.
> To view this discussion on the web visit
> https://groups.google.com/d/msgid/elixir-lang-core/bc95dc57-2df1-4b32-b673-535e5c499493n%40googlegroups.com
> <https://groups.google.com/d/msgid/elixir-lang-core/bc95dc57-2df1-4b32-b673-535e5c499493n%40googlegroups.com?utm_medium=email&utm_source=footer>
> .
>

-- 
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-core+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/elixir-lang-core/CAGnRm4K5_PDeWk%3Dis26c%3Dc0OwFDrfy8LfjwQivriccxq2vKWZg%40mail.gmail.com.

Reply via email to