New function lists:most/1

I ran into a problem where I had to find out the elements which were repeated the most in the list, sort them and drop the duplicates.
I was scanning through the lists module and was kinda surprised that no such function exist.
For example I have a list:

[1,2,2]

Expected end result:

[2,1]

I solved it with the following code:

most(List) ->
    ListCounted = lists:foldl(
        fun(El, Acc) -> maps:put(El, maps:get(El, Acc, 0) + 1, Acc) end,
        #{}, 
        List),
    ListSorted = lists:sort(
        fun({_, Acount}, {_, Bcount}) -> Acount > Bcount end,
        maps:to_list(ListCounted)),
    [Value || {Value, _Count} <- ListSorted].

I think the code can be better but I wanted to provide an example.

Maybe there should be a function in the lists module that does exactly that.
Just an idea :slight_smile:

1 Like

That sounds like an extremely specialized use-case, not really suited for the standard library.

8 Likes

I’m not sure you’d find a standard function for that in any language. Although lists:uniq/1 will remove duplicates.

2 Likes

Thanks for the replies.
Perhaps it isn’t that common problem.

lists:uniq/1 would not have worked in my case. I had to find the most popular element from the list.

1 Like

Would lists:reverse(lists:sort(lists:uniq([1,2,2]))) not work for you? It works for that one test case, but I’ve not tried it for others.

Edit ah, you want to count the most common, so no it wouldn’t.

2 Likes

Here’s how it works in Smalltalk
aCollection asBag
“converts any collection to an {element → count} table”
sortedCounts
"converts to a sequence of element->count associations in

decreasing order of count"
collect: [:each | each key]
“strips off the counts”
This is in a language with more kinds of collections than you’ve dreamed
of and more operations on them than Erlang’s worst nightmares. The
combination you want is not there but the pieces you need to build it
trivially are.

So look for suitable pieces that might be useful to other people and
recommend those.

1 Like

Same solution that I came up. Although I feel my code is kinda clunky :slight_smile:
Lots of code for such simple operation(order elements by popularity).
I have checked some examples in other languages and the solutions seemed to be having same amount of code.
Probably not so common issue.

Not sure if this helps, but here you have a one-liner built on top of the new maps:groups_from_list/2 that was introduced in OTP25:

most(List) ->
    [[X | _] | _] =
        lists:sort(
            fun(L1, L2) -> length(L1) > length(L2) end,
            maps:values(
                maps:groups_from_list(
                    fun(X) -> X end, List))),
    X.

…or…

most(List) ->
    hd(hd(
        lists:sort(
            fun(L1, L2) -> length(L1) > length(L2) end,
            maps:values(
                maps:groups_from_list(
                    fun(X) -> X end, List))))).
4 Likes
most(List) ->
    M = lists:foldl(fun(E, A) -> maps:update_with(E, fun(V) -> V + 1 end, 1, A) end, #{}, List),
    lists:sort(fun(A, B) -> maps:get(A, M) >= maps:get(B, M) end, maps:keys(M)).
2 Likes

OK there is a more general issue here.
Since the counts are positive integers bounded by the
length of the original list, it ought to be possible to
do the whole thing in linear time. But there is no
“sort by modest integer keys” function in the library.

BTW, I am having a hard time believing that it is useful
to answer [x,y] with no indication of whether x occured a
million times more often than y or the same number of times.

1 Like