Peter’s blog ✴ Week 322 ✴ 19 May 2025

THE WEEKLY CHALLENGE
More strings and arrays

The Perl Camel

Task 2

Rank array

You are given an array of integers. Write a script to return an array of the ranks of each element: the lowest value has rank 1, next lowest rank 2, etc. If two elements are the same then they share the same rank.

Examples


Example 1
Input: @ints = (55, 22, 44, 33)
Output: (4, 1, 3, 2)

Example 2
Input: @ints = (10, 10, 10)
Output: (1, 1, 1)

Example 3
Input: @ints = (5, 1, 1, 4, 3)
Output: (4, 1, 1, 3, 2)

Analysis

My algorithm is as follows:

Create a hash %numbers with the numbers from the input array as the keys. This automatically eliminates duplicates.

Create an array @rank like this:

$rank[$_] = $j ++ for sort {$a <=> $b} keys %numbers;

which creates an array @rank such that the rank of value $array[$i] is $rank[$i] - for example, the rank of the lowest unique value in the input array is 1, the next lowest 2 and so on.

Create the desired output array, @ranking, by setting:

$ranking[$_] = $rank[$array[$_]] for 0 .. $#array;

which creates the desired output array, @ranking where $ranking[$i] contains the rank of element $i in the input array.

This works for any input array values that are positive, but the challenge states 'integers' rather than 'positive integers'. To cope with that, I first determine the minimum value $min in @array and then offset the indices of @rank by $min so that they are never negative.

Perl Weekly’s review

from Perl Weekly issue 722

Each solution is accompanied by concise reasoning, making it easy to follow the logic and adapt the code for similar problems.

Try it 

Try running the script with any input:



example: 3, 1, 4, 1, 5, 9

Script


#!/usr/bin/perl

# Blog: http://ccgi.campbellsmiths.force9.co.uk/challenge

use v5.26;    # The Weekly Challenge - 2025-05-19
use utf8;     # Week 322 - task 2 - Rank array
use warnings; # Peter Campbell Smith
binmode STDOUT, ':utf8';
use Encode;
use List::Util 'min';

rank_array(55, 22, 44, 33);
rank_array(10, 10, 10);
rank_array(5, 1, 1, 4, 3);
rank_array(0);
rank_array(-3, -3, -3, -2, -2, -1, 0);

# bigger example
my @array;
push @array, int(rand(100)) for 0 .. 25;
rank_array(@array);

sub rank_array {
    
    my (@array, @rank, @ranking, $n, $j, %numbers, $min);
    
    # initialise
    @array = @_;
    
    # to cope with -ve numbers
    $min = min(@array);
    
    # make # with keys of unique numbers
    $numbers{$_} = 1 for @array;
    
    # rank the numbers smallest to largest
    $j = 1;
    $rank[$_ - $min] = $j ++ for sort {$a <=> $b} keys %numbers;
    
    # assign the ranks to @ranking in the original order
    $ranking[$_] = $rank[$array[$_] - $min] for 0 .. $#array;
    
    say qq[\nInput:  (] . join(', ', @array) . q[)];
    say qq[Output: (] . join(', ', @ranking) . q[)];
}


10 lines of code

Output from script


Input:  (55, 22, 44, 33)
Output: (4, 1, 3, 2)

Input:  (10, 10, 10)
Output: (1, 1, 1)

Input:  (5, 1, 1, 4, 3)
Output: (4, 1, 1, 3, 2)

Input:  (0)
Output: (1)

Input:  (-3, -3, -3, -2, -2, -1, 0)
Output: (1, 1, 1, 2, 2, 3, 4)

Input:  (8, 57, 77, 28, 10, 65, 28, 3, 67, 68, 24, 84, 52,
   80, 64, 66, 80, 81, 95, 53, 47, 83, 57, 81, 86, 11)
Output: (2, 10, 16, 6, 3, 12, 6, 1, 14, 15, 5, 20, 8, 17,
   11, 13, 17, 18, 22, 9, 7, 19, 10, 18, 21, 4)

 

Any content of this website which has been created by Peter Campbell Smith is in the public domain