Optional filter function for Enum.max and Enum.min

263 views
Skip to first unread message

Patrick Oscity

unread,
Jun 11, 2015, 4:39:28 AM6/11/15
to elixir-l...@googlegroups.com
Hi all,

say you want to find the length of the longest string in a list, you can do:

    Enum.map(list, &String.length/1) |> Enum.max

or you can do:

    Enum.max_by(list, &String.length/1) |> String.length

I think it's nice that &Enum.max_by/2 takes a filter function as optional second
argument so we don't need to iterate the collection twice. However, both of the
solutions above feel a bit too clunky in my opinion.

How about we unify the arguments of Enum.max and Enum.max_by, so we
can provide a filter function as optional second argument. Then we could write

    Enum.max(list, &String.length/1)

Of course, the same thing applies to Enum.min. Let me know what you think
and I'd be happy to make my first pull request to Elixir ;-)

Best,
Patrick

José Valim

unread,
Jun 11, 2015, 5:40:52 AM6/11/15
to elixir-l...@googlegroups.com
Unfortunately this starts a slippery slope of "why not add filter to all functions in Enum?". You have three options here: 1. just traverse twice, it is fine for small lists. 2. use the Stream module or 3. Use a custom reduce.

There is another option which is to provide a custom data structure that implements the collectable protocol to calculate the max, which is stored in the max_data variable below. Then you can use it in a comprehension:

    for x <- list, some_filter?(x), do: x, into: max_data

I have actually had plans since ever to make this a bit more convenient but I never found the right abstraction for it. At best, it could be:

    for x <- list, some_filter?(x), do: x, into: {0, &max/2}

Where the tuple represents the Initial value and the reduce function. This may become more relevant once we add more stream functions in the future.


--


José Valim
Skype: jv.ptec
Founder and Lead Developer

Patrick Oscity

unread,
Jun 11, 2015, 7:11:08 AM6/11/15
to elixir-l...@googlegroups.com, jose....@plataformatec.com.br
While I would personally find this particular addition useful, I understand that continuously adding
more options everywhere isn't going to make Elixir a better language in the long run.

Good thing that you mentioned a custom reduce, I hadn't immediately thought about that. Here is
a solution that is reasonably simple and does the job:

    Enum.reduce(list, 0, &max(String.length(&1), &2))

Your idea about the collectable data structure sounds great. It is definitely preferable to have
something re-usable and composable instead of adding options.

Thanks anyway, you gave me some new ideas!

Ben Wilson

unread,
Jun 11, 2015, 10:25:47 AM6/11/15
to elixir-l...@googlegroups.com, jose....@plataformatec.com.br
This is a great case for streams.

["a", "foo", "asdfasdf"]
|> Stream.map(&String.length/1)
|> Enum.max

I've been using that in cases where I want to do a map and a find. This is only a good idea if your list is very long, otherwise just iterate twice.

Peter Hamilton

unread,
Jun 11, 2015, 10:39:24 AM6/11/15
to elixir-l...@googlegroups.com, jose....@plataformatec.com.br

The problem with the Stream approach is you lose the original item. If you want the max length then its fine. If you want the longest string then you are sort of stuck.


--
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/49ad9d2d-9f4f-4f0c-a229-7a6357a03c3c%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Ben Wilson

unread,
Jun 11, 2015, 11:00:31 AM6/11/15
to elixir-l...@googlegroups.com, jose....@plataformatec.com.br
Oh sure I was just going for a single pass version of his original examples which all result in the length itself, not the item with the longest length. If he wanted the latter he can just use the single pass Enum.max_by/2

Peter Hamilton

unread,
Jun 11, 2015, 11:25:01 AM6/11/15
to elixir-l...@googlegroups.com, jose....@plataformatec.com.br

I misunderstood the original post. I thought it was a request for a more universal *_by api (beyond min_by and max_by). My (possibly unrelated) point was that you can't easily mimic max_by without a custom reduce.

Re: the original proposal, we already have uniq, all?, and any? that have the optional second argument. I wouldn't be opposed to making that standard.


Reply all
Reply to author
Forward
0 new messages