Back to homepage | Back to blog | Back to archive

Ranking list algorithm in Elixir

Written by Adrian Kjær Dalhaug at 2020-02-19 14:19:37 UTC.


An interesting problem landed on my desk at work today: Make a ranking list algorithm in Elixir.

Rules:

  1. The ranking starts at 1.
  2. If two or more participants share the same number of points, they should get the same ranking.
  3. The ranking should indicate how many participants are ranked higher than any given rank.

The last rule is interesting in that there will be gaps in the ranking if rule #2 gets into play.

Example:

Call for proposals

I made my solution in Elixir because that's what the current project uses. I'd love it if someone out there would send me an email with a link to an implementation in their favourite language. I'll add any contributions below.

EDIT: Got a bunch of responses on Lobsters with great solutions to the problem :)

My solution (Elixir script - .exs)

defmodule Ranking do
  def ranking(participants) do
    participants
    |> Enum.sort(&(&1.points >= &2.points))
    |> ranking_work()
  end

  defp ranking_work(
         participants,
         prev_points \\ 0,
         prev_ranking \\ 0,
         prev_count \\ 1,
         ranking_list \\ []
       )

  defp ranking_work([], _, _, _, ranking_list) do
    ranking_list
  end

  defp ranking_work(
         [participant | other_participants],
         prev_points,
         prev_ranking,
         prev_count,
         ranking_list
       ) do
    {new_points, new_ranking, new_count, new_ranking_list} =
      if prev_points == participant.points do
        new_ranking = format_rank(prev_ranking)

        {prev_points, new_ranking, prev_count + 1,
         ranking_list ++ [Map.merge(participant, %{ranking: new_ranking})]}
      else
        new_ranking = format_rank(prev_ranking + prev_count)

        {participant.points, new_ranking, 1,
         ranking_list ++ [Map.merge(participant, %{ranking: new_ranking})]}
      end

    ranking_work(other_participants, new_points, new_ranking, new_count, new_ranking_list)
  end

  defp format_rank(rank) do
    # even if every person has 0 in rank, never use 0 as rank

    if rank == 0 do
      1
    else
      rank
    end
  end

  def rank_mock_data() do
    [
      %{id: 1, points: 39},
      %{id: 2, points: 26},
      %{id: 3, points: 50},
      %{id: 4, points: 24},
      %{id: 5, points: 39},
      %{id: 6, points: 67},
      %{id: 7, points: 39},
      %{id: 8, points: 50},
      %{id: 9, points: 48},
      %{id: 10, points: 24}
    ]
    |> ranking()
  end
end

Ranking.rank_mock_data()
|> inspect(pretty: true)
|> IO.puts()

Output

$ elixir ranking.exs

[
  %{id: 6, points: 67, ranking: 1},
  %{id: 3, points: 50, ranking: 2},
  %{id: 8, points: 50, ranking: 2},
  %{id: 9, points: 48, ranking: 4},
  %{id: 1, points: 39, ranking: 5},
  %{id: 5, points: 39, ranking: 5},
  %{id: 7, points: 39, ranking: 5},
  %{id: 2, points: 26, ranking: 8},
  %{id: 4, points: 24, ranking: 9},
  %{id: 10, points: 24, ranking: 9}
]

Comments? Mail them to: adrian@enpo.no.

Generated at 2020-02-19 14:19:38 UTC.