1. 11
1. 5

Here you have much smaller, and IMHO much more readable example of the same:

``````defmodule Ranking do
def ranking(participants) do
participants
|> Enum.sort(&(&1.points >= &2.points))
|> Enum.map_reduce({nil, 0, 0}, &do_ranking/2)
|> elem(0)
end

defp do_ranking(%{points: points} = curr, {%{points: points}, rank, count}) do
count = count + 1
{Map.put(curr, :ranking, rank), {curr, rank, count}}
end

defp do_ranking(curr, {_, rank, count}) do
next_rank = rank + count + 1
{Map.put(curr, :ranking, next_rank), {curr, next_rank, 0}}
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()
``````

EDIT: Earlier version was slightly buggy, as it didn’t returned ranks always increased by 1, this fixes it.

1. 1

Cool! Thanks for sharing :)

2. 3

Inspired by @geocar, here’s the J version. It assumes the values are stored in an Nx2 array where the first column is the score and the second is the id:

``````r ,. >./\(* >:@i.@#) ~: 0{"1 r =: \:~ x
``````

This makes the ranking column three. There’s probably a more elegant way to do this but I’m on my phone rn. Breakdown:

1. Sort the participants by score, descending
2. Get the “nub seive”, the bitstring corresponding to the indices with the first occurrence of each element
3. Multiply the bitstring by the index array. First occurrences become their index, duplicates remain zero.
4. Get the running maximum of the list, which replaces zeros with the index of the first occurrence
5. Combine that with the original sorted array.
1. 3

Perl5:

``````#! /usr/bin/env perl
use Modern::Perl '2015';
###

my %hist;
my \$id = 1;
while (<DATA>) {
chomp;
push @{\$hist{\$_}}, \$id;
\$id++;
}
my \$rank =1;
foreach my \$score (sort {\$b<=>\$a} keys %hist) {
foreach my \$id (@{\$hist{\$score}}) {
printf("id: %2d, points: %2d, ranking: %d\n",
\$id, \$score,\$rank
);
}
\$rank += scalar @{\$hist{\$score}};
}
__DATA__
39
26
50
24
39
67
39
50
48
24
``````
1. 2

That encouraged me to do it in (mostly) AWK:

``````\$ cat ranking.txt
1,39
2,26
3,50
4,24
5,39
6,67
7,39
8,50
9,48
10,24

\$ cat ranking.awk
BEGIN {
FS = ",";
OFS = ",";

ranking = 0;
count = 0;
last_points = 0;

print "ranking", "points", "id";
}

{
if (last_points == \$2) {
count++;
} else {
ranking += count + 1;
count = 0;
last_points = \$2;
};

if (ranking == 0) {
ranking = 1;
};

print ranking, \$2, \$1;
}

\$ sort -t, -k2nr ranking.txt | awk -f ranking.awk
ranking,points,id
1,67,6
2,50,3
2,50,8
4,48,9
5,39,1
5,39,5
5,39,7
8,26,2
9,24,10
9,24,4
``````
2. 2

Here’s another Elixir implementation, similar to @hauleth’s!

By using `Enum.group_by/2` up front, our reduce step is a little simpler, because the rank of every set of data is the rank of the prior set plus the length of the prior set. I also changed the output a little bit, it’s just a map whose keys are the ranks, and the values are the data at that rank.

``````defmodule Ranking do
@mock_data [
%{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}
]

def mock_data do
@mock_data
end

def rank(data) do
data
# Gives us a list of {points, data} tuples
|> Enum.group_by(& &1.points)
|> Enum.sort(&(elem(&1, 0) > elem(&2, 0)))
|> Enum.reduce({1, %{}}, fn {_points, data}, {next_rank, ranking} ->
{next_rank + length(data), Map.put(ranking, next_rank, data)}
end)
|> elem(1)
end
end

Ranking.mock_data()
|> Ranking.rank()
|> IO.inspect()
``````

And here’s a similar approach in JavaScript:

``````const mockData = [
{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}
]

function rank(data) {
const grouped = data.reduce((grouped, datum) => {
if (!grouped.get(datum.points)) grouped.set(datum.points, [])
grouped.get(datum.points).push(datum)
return grouped
}, new Map()) // Use a Map so we don't have to parseInt on object keys

const sorted = Array.from(grouped.entries()).sort(([a], [b]) => b - a)

return sorted.reduce(([nextRank, ranking], [, data]) => {
ranking[nextRank] = data
return [nextRank + data.length, ranking]
}, [1, {}])
}

console.log(JSON.stringify(rank(mockData), null, 2))
``````
1. 1

My problem with that solution is that it needlessly iterates through all data at least 3 times (1 for `Enum.group_by/2`, 1 for `sort/2`, and 1 for `length/1` inside `Enum.reduce/3`). This can be a little bit of the problem.

1. 1

That’s true! A good point.

I think the original solution linked iterates over the data twice (sorting, then the iteration over each item using recursion). This solution is at worst once over the data to group by score, then twice over the groups, which is indeed worst-case an extra iteration than the linked solution if each piece of the data has a unique point value within the data. It only iterates over all the data 3 times in this case (each piece of data has a unique point value). The second and third iterations are actually sized by the unique point values in the data set. They’re both still O(n), though.

2. 1

My solution:

``````items
|> Enum.sort_by(& &1.points * -1)
|> Enum.map_reduce(nil, fn
item, nil -> {Map.put(item, :ranking, 1), {1, item.points, 1}}
%{points: points} = item, {rank, points, count} -> {Map.put(item, :ranking, rank), {rank, points, count + 1}}
%{points: points} = item, {rank, _, count} -> {Map.put(item, :ranking, rank + count), {rank + count, points, 1}}
end)
|> elem(0)
|> IO.inspect()
``````
1. 1

Nice trick with that `Enum.sort_by/2`. I used tuple in my solution to not need to handle `item, nil` as a separate case. I thought about using pattern match in the closure, but I thought that separate helper function will be clearer.

2. 1

Come on guys, there are data structures for this sort of thing…

``````\$ cat <<'EOF' > ranking.erl
#!/usr/bin/env escript

-define(DATA, [
#{ 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 }
]).

main([]) ->
Data0 = lists:foldl(fun(X, A) ->
orddict:append(1 / maps:get(points, X), X, A)
end, orddict:new(), ?DATA),
{_Rank, Data} = orddict:fold(fun(_K, V0, {R, A}) ->
V = lists:map(fun(X) -> X#{ ranking => R } end, V0),
{R + length(V), lists:append(A, V)}
end, {1, []}, Data0),
io:format("~p~n", [Data]),
halt(0).
EOF

\$ escript ranking.erl
[#{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}]
``````
1. 4

Challenge accepted!

``````q)t:([] id:1+til 10; points:39 26 50 24 39 67 39 50 48 24)
q)ungroup select id,points,first each rnk from `points xgroup update rnk:1+i from `points xdesc t
id points rnk
-------------
6  67     1
3  50     2
8  50     2
9  48     4
1  39     5
5  39     5
7  39     5
2  26     8
4  24     9
10 24     9
``````