For some reason, this seems off to me. It seems to me like something like a.) 
allowing enumerable implementors to override the method of splitting, and then 
b.) allowing `split_with` to specify an option that says "I expect the original 
type back", might be better? My perspective here is based on making the 
structures that we use regularly more powerful/smarter as opposed to splitting 
that responsibility up into the other modules. I'll have to forever remember 
that `Map.split_with` performs better for maps than `Enum.split_with` and to 
use that instead if I'm using a map. Whereas `Enum.split_with(map, func, into: 
%{})` (just using that option name as an example, I don't really think it is 
good).

Sent via Superhuman ( https://sprh.mn/?vip=zachary.s.dan...@gmail.com )

On Tue, Dec 14, 2021 at 4:02 AM, José Valim < jose.va...@dashbit.co > wrote:

> 
> 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. yr@ gmail. com (
> 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 ( http://map.new/ ) (0..1000, fn n -> {n, n} end)
>> predicate = fn {x, _} -> rem(x, 2) == 0 end
>> 
>> Benchee. run ( http://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 ( http://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 (
>> http://maps.next/ ) (iter), fun)
>> else
>> do_split_with({while_true, [{key, value} | while_false]}, : maps. next (
>> http://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 ( http://map.new/ ) (while_true), Map. new ( http://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 ( http://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+unsubscribe@ googlegroups. com (
>> 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+unsubscribe@ googlegroups. com (
> 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 (
> https://groups.google.com/d/msgid/elixir-lang-core/CAGnRm4K5_PDeWk%3Dis26c%3Dc0OwFDrfy8LfjwQivriccxq2vKWZg%40mail.gmail.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/kx6eh52b.08d97836-dfc5-45ff-a864-6ee2795f5a92%40we.are.superhuman.com.

Reply via email to