Perl Weekly Challenge: Week 182

Challenge 1:

Max Index

You are given a list of integers.

Write a script to find the index of the first biggest number in the list.

Example
Input: @n = (5, 2, 9, 1, 7, 6)
Output: 2 (as 3rd element in the list is the biggest number)


Input: @n = (4, 2, 3, 1, 5, 0)
Output: 4 (as 5th element in the list is the biggest number)

I followed the same pattern as I used for challenge 2 last week i.e. going through the list and comparing each value to the one before it. There are two differences...

First is that this time we want the highest value not the lowest. So we set our initial "best" value to negative infinity, the lowest possible number.

my $max = -∞;

Second is that the spec doesn't want us to find the biggest number itself but its' index in the array. We can do this by traversing the list using the .pairs() method which will give us a Pair of the index of an element and its value. We also keep track of the best index when we are updating the best value.

my $index;

for @n.pairs -> $i {
    if $i.value > $max  {
        $max = $i.value;
        $index = $i.key;
    }
}

say $index;

(Full code on Github.)

This is the Perl version. each() provides tha same functionality as .pairs() did in Raku.

my $max = "-Inf";
my $index;

while (my ($key, $value) = each @n) {
    if ($value > $max)  {
        $max = $value;
        $index = $key;
    }
}

say $index;

(Full code on Github.)

Challenge 2:

Common Path

Given a list of absolute Linux file paths, determine the deepest path to the directory that contains all of them.

Example
Input:
    /a/b/c/1/x.pl
    /a/b/c/d/e/2/x.pl
    /a/b/c/d/3/x.pl
    /a/b/c/4/x.pl
    /a/b/c/d/5/x.pl

Ouput:
    /a/b/c

This task seemed vaguely familiar so I looked back and sure enough I found that the same problem had been asked (albeit with different wording) three years ago in PWC 12. I merely had to copy my solutions from then to this week and I was done. Unfortunately it seems I had only started blogging the week after but uncharacteristically for these challenges I had left comments in the Perl code. (I swear I am better in production code.) So this blog entry also practically wrote itself!

The file paths are entered as command line arguments. The first step is to sort them so the shortest is first because because the common directory path cannot be longer than the shortest path.

Then each path is split into an array of path segments and those arrays are themselves put into an array called @path.

my @paths = map { [ split q{/} ] } reverse sort @ARGV;

Now the same segment of each path is compared. If they are all the same, the segment is part of the common directory path. If it isn't, we can stop.

my @commonDirectoryPath;
for my $segment (0 .. scalar @{$paths[0]} - 1) {
    my $dir = @{$paths[0]}[$segment];
    if (!scalar grep { !/$dir/ } map { @{$_}[$segment] } @paths) {
        push @commonDirectoryPath, $dir;
    } else {
        last;
    }
}

Whatever we got for the common directory path is printed with the directory separator re-added. if the common directory path was empty i.e. there were no matches in the previous step, the common directory path is /.

say scalar @commonDirectoryPath > 1 ? join q{/}, @commonDirectoryPath : q{/};

(Full code on Github.)

I had to change references to Perl 6 in the old code to Raku but otherwise it held up pretty well. I only had to make a few small changes to reflect things I know how to do better now.

sub MAIN(
+@args where { $_.elems && $_.all ~~ /^\// }  #= File paths.
) {
    my @paths = @args
                .sort
                .reverse
                .map({ $_.split('/') });

    my @commonDirectoryPath;
    for 0 ..^ @paths[0].elems -> $segment {
        my $dir = @paths.first()[$segment];
        if @paths.map({ $_[$segment] }).all eq $dir {
            push @commonDirectoryPath, $dir;
        } else {
            last;
        }
    }

    say @commonDirectoryPath.elems > 1 ?? @commonDirectoryPath.join('/') !! '/';
}

(Full code on Github.)