Perl Weekly Challenge 284: Relative Sort

These are some answers to the Week 284, Task 2, of the Perl Weekly Challenge organized by Mohammad S. Anwar.

Spoiler Alert: This weekly challenge deadline is due in a few days from now (on September 1, 2024, at 23:59). This blog post provides some solutions to this challenge. Please don’t read on if you intend to complete the challenge on your own.

Task 2: Relative Sort

You are given two list of integers, @list1 and @list2. The elements in the @list2 are distinct and also in the @list1.

Write a script to sort the elements in the @list1 such that the relative order of items in @list1 is same as in the @list2. Elements that is missing in @list2 should be placed at the end of @list1 in ascending order.

Example 1

Input: @list1 = (2, 3, 9, 3, 1, 4, 6, 7, 2, 8, 5)
       @list2 = (2, 1, 4, 3, 5, 6)
Ouput: (2, 2, 1, 4, 3, 3, 5, 6, 7, 8, 9)

Example 2

Input: @list1 = (3, 3, 4, 6, 2, 4, 2, 1, 3)
       @list2 = (1, 3, 2)
Ouput: (1, 3, 3, 3, 2, 2, 4, 4, 6)

Example 3

Input: @list1 = (3, 0, 5, 0, 2, 1, 4, 1, 1)
       @list2 = (1, 0, 3, 2)
Ouput: (1, 1, 1, 0, 0, 3, 2, 4, 5)

Relative Sort in Raku

Instead of sorting @list1 in accordance with the order of @list2, we will duplicate @list2 with each item of @list2 being repeated the number of times it occurs un @list1. At the end, we append to the resulting list the items (sorted in ascending order) of @list1 not used previously.

We first need to create an histogram of frequencies of @list1, which can be easily done with Bag, as we've done previously (for example in the lucky number task of this week's challenge. The only slight problem is that we also want to use this histogram data structure to track the items that we have already used and know which items are left. A Bag is immutable, so we will use its mutable variant, a BagHash instead. So, once we use an item, we set its frequency to 0 and the item is automatically removed from the BagHash.

Note that, although we are told that the elements of @list2 are all in @list1, we nonetheless check to avoid an exception, but don't do anything about it (we don't know what should be done) and let the program run to its end. Also note that we print out only @list2 with the results, for space reasons.

sub relative-sort (@in1, @in2) {
    my @result;
    my $to-be-sorted = @in1.BagHash;
    for @in2 -> $i {
        if $to-be-sorted{$i}:exists {
            append @result, $i xx $to-be-sorted{$i};
            $to-be-sorted{$i} = 0;
        }
    }
    append @result, (map { $_ xx $to-be-sorted{$_}}, $to-be-sorted.keys).sort;
    return "@result[]";
}

my @tests = ((2, 3, 9, 3, 1, 4, 6, 7, 2, 8, 5), (2, 1, 4, 3, 5, 6)),
            ((3, 3, 4, 6, 2, 4, 2, 1, 3), (1, 3, 2)),
            ((3, 0, 5, 0, 2, 1, 4, 1, 1), (1, 0, 3, 2));
for @tests -> @test {
    printf "%-15s => ", "@test[1]";
    say relative-sort @test[0], @test[1];
}

This program displays the following output:

$ raku ./relative-sort.raku
2 1 4 3 5 6     => 2 2 1 4 3 3 5 6 7 8 9
1 3 2           => 1 3 3 3 2 2 4 4 6
1 0 3 2         => 1 1 1 0 0 3 2 4 5

Relative Sort in Perl

This is a port to Perl of the above Raku program. Please refer to the previous section if you need explanations. Instead of a BagHash, we use in Perl a regular hash.

use strict;
use warnings;
use feature 'say';

sub relative_sort {
    my ($in1, $in2) = @_;
    my @result;
    my %unsorted;
    $unsorted{$_}++ for @$in1;
    for my $i (@$in2) {
        if (exists $unsorted{$i}) {
            push @result, ($i) x $unsorted{$i};
            $unsorted{$i} = 0;
        }
    }
    push @result, sort map { ($_) x $unsorted{$_}} 
        grep { $unsorted{$_} > 0 } keys %unsorted;
    return "@result";
}

my @tests = ( [[2, 3, 9, 3, 1, 4, 6, 7, 2, 8, 5], [2, 1, 4, 3, 5, 6]],
              [[3, 3, 4, 6, 2, 4, 2, 1, 3], [1, 3, 2]],
              [[3, 0, 5, 0, 2, 1, 4, 1, 1], [1, 0, 3, 2]] );
for my $test (@tests) {
    printf "%-15s => ", "@{$test->[1]}";
    say relative_sort @$test[0], @$test[1];
}

This program displays the following output:

$ perl  ./relative-sort.pl
2 1 4 3 5 6     => 2 2 1 4 3 3 5 6 7 8 9
1 3 2           => 1 3 3 3 2 2 4 4 6
1 0 3 2         => 1 1 1 0 0 3 2 4 5

Wrapping up

The next week Perl Weekly Challenge will start soon. If you want to participate in this challenge, please check https://perlweeklychallenge.org/ and make sure you answer the challenge before 23:59 BST (British summer time) on September 9, 2024. And, please, also spread the word about the Perl Weekly Challenge if you can.

Leave a comment

About laurent_r

user-pic I am the author of the "Think Perl 6" book (O'Reilly, 2017) and I blog about the Perl 5 and Raku programming languages.