Perl Weekly Challenge 182: Max Index and Common Path

These are some answers to the Week 182 of the Perl Weekly Challenge organized by Mohammad S. Anwar.

Spoiler Alert: This weekly challenge deadline is due in a few of days from now (on Sept. 18, 2022 at 23:59). This blog post offers some solutions to this challenge, please don’t read on if you intend to complete the challenge on your own.

Task 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)

Max Index in Raku

The initial idea here was to to store the input data into a hash and to use the max routine to find the maximum value. The max documentation says:

Coerces the invocant to Iterable and returns the numerically largest element; in the case of Hashes, the Pair with the highest value.

But that did not work properly in my tests: max consistently appeared to return the pair with the highest key, not the highest value. I tried to use the maxpairs routine, which, according to the documentation,

returns a Seq with all of the Pairs with maximum value.

The maxpairs method works as expected. This leads to a very short program:

for (5, 2, 9, 1, 7, 6), (4, 2, 3, 1, 5, 0) -> @test {
    my %nums = @test.kv;
    say "@test[] : ", %nums.maxpairs;
}

This program displays the following output:

$ raku ./max-index.raku
5 2 9 1 7 6 : (2 => 9)
4 2 3 1 5 0 : (4 => 5)

Max Index in Perl

In Perl, we use a standard for loop to traverse the input list and find the index of the largest value:

use strict;
use warnings;
use feature qw/say/;

for my $test ([5, 2, 9, 1, 7, 6], [4, 2, 3, 1, 5, 0]) {
    my @nums = @{$test};
    my ($max_i, $max_n) = (0, 0);
    for my $i (0..$#nums) {
        if ($nums[$i] > $max_n) {
            $max_n = $nums[$i];
            $max_i = $i;
        }
    }
    say "@nums : $max_i => $max_n";
}

This program displays the following output:

$ perl ./max-index.pl
5 2 9 1 7 6 : 2 => 9
4 2 3 1 5 0 : 4 => 5

Max Index in 7 Other Languages

We have now implementations of the max index task in 7 languages as follows:

  • Awk
  • JavaScript
  • Julia
  • Python
  • Ring
  • Ruby
  • Scala

Max Index in Awk

In awk, the data is normally passed as standard input. So we use a Unix pipe to pass the data to the program.

# run for example as:
# echo '5 2 9 1 7 6
# 4 2 3 1 5 0' | awk -f ./max-index.awk

function find_max() {
    max_i = 0
    max_n = $1
    for (i = 2; i < NF; i++) {
        if ($i > max_n) {
            max_i = i
            max_n = $i
        }
    }
    printf("Max index for %s: %d => %d\n", $0, max_i - 1, max_n)
}
{
    find_max()
}

Output:

$ echo '5 2 9 1 7 6
4 2 3 1 5 0' | awk -f ./max-index.awk
Max index for 5 2 9 1 7 6: 2 => 9
Max index for 4 2 3 1 5 0: 4 => 5

Max Index in JavaScript

let tests = [ [5, 2, 9, 1, 7, 6], [4, 2, 3, 1, 5, 0] ]
for  (let j = 0; j < tests.length; j++) {
    let test = tests[j]
    let max_i = 0
    let max_n = test[0]
    for (let i = 1; i <= test.length; i++) {
        if (test[i] > max_n) {
            max_n = test[i]
            max_i = i
        }
    }
    console.log("Max index for " + test + ": " + max_i + " => " + max_n)
}

Output:

Max index for 5,2,9,1,7,6: 2 => 9
Max index for 4,2,3,1,5,0: 4 => 5

Max Index in Julia

Note that Julia indexes start at 1. So we need to subtract 1 from the max_i variable to get a result consistent with other (0-based index) languages.

for test in [[5, 2, 9, 1, 7, 6], [4, 2, 3, 1, 5, 0]]
    max_i = 1
    max_n = test[1]
    for i in 2:length(test)
        if (test[i] > max_n)
            max_n = test[i]
            max_i = i
        end
    end
    println("Max index for $test: $(max_i - 1) => $max_n)")
end

Output:

$ julia ./max-index.jl
Max index for [5, 2, 9, 1, 7, 6]: 2 => 9)
Max index for [4, 2, 3, 1, 5, 0]: 4 => 5)

Max Index in Python

for test in [5, 2, 9, 1, 7, 6], [4, 2, 3, 1, 5, 0]:
  max_i = 0
  max_n = test[0]
  for i in range(1, len(test)):
    if test[i] > max_n:
      max_n = test[i]
      max_i = i
  print("Max index for ", test, ": ", max_i, " => ", max_n)

Output:

$ python3 ./max-index.py
Max index for  [5, 2, 9, 1, 7, 6] :  2  =>  9
Max index for  [4, 2, 3, 1, 5, 0] :  4  =>  5

Max Index in Ring

Note that Ring indexes start at 1 (like Julia indexes). So we need to subtract 1 from the max_i variable to get a result consistent with other (0-based index) languages.

for test in [[5, 2, 9, 1, 7, 6], [4, 2, 3, 1, 5, 0]]
    max_i = 1
    max_n = test[1]
    str = ""
    for i = 1 to len(test)
        str = str + test[i] + " "
        if test[i] > max_n
            max_n = test[i]
            max_i = i
        ok
    next
    see "Max index for " + str + ": " +
        (max_i - 1) + " => " + max_n + nl
next

Output:

$ ring ./max-index.ring
Max index for 5 2 9 1 7 6 : 2 => 9
Max index for 4 2 3 1 5 0 : 4 => 5

Max Index in Ruby

for test in [[5, 2, 9, 1, 7, 6], [4, 2, 3, 1, 5, 0]]
    max_i = 0
    max_n = test[0]
    for i in 1..(test.length - 1)
        if test[i] > max_n
            max_n = test[i]
            max_i = i
        end
    end
    printf("Max index for %s: %d => %d\n", test.to_s, max_i, max_n)
end

Output:

Max index for [5, 2, 9, 1, 7, 6]: 2 => 9
Max index for [4, 2, 3, 1, 5, 0]: 4 => 5

Max Index in Scala

object fraction_tree extends App {

  val tests: List[List[Int]] =
    List(List(5, 2, 9, 1, 7, 6), List(4, 2, 3, 1, 5, 0))
  for (test <- tests) {
    var max_i = 0
    var max_n = test(max_i)
    for (i <- 1 to test.length - 1) {
      if (test(i) > max_n) {
        max_n = test(i)
        max_i = i
      }
    }
    println("Max index for " + test.mkString(" ") + s": $max_i => $max_n")
  }
}

Output:

Max index for 5 2 9 1 7 6: 2 => 9
Max index for 4 2 3 1 5 0: 4 => 5

Task 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

Common Path in Raku

This program converts the input into an array of arrays and then compares each item of the first line with the corresponding item of the other lines. The program stops as soon as a difference is found.

my @input = qw <
    /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
    >;

my @paths = gather {
    for @input <-> $line {
        $line ~~ s/^'/'//;
        my @subpaths = split /'/'/, $line;
        take @subpaths[0..*-2];
    }
}
my $end = @paths.end;
my $k = 0;
OUTLOOP: for 0..(@paths[0].end) -> $i {
    for 0..$end -> $j {
        if @paths[$j][$i]:!exists or @paths[$j][$i] ne @paths[0][$i] {
            $k = $i - 1;
            last OUTLOOP;
        }
    }
}
say '/', join '/', @paths[0][0..$k];

This program displays the following output:

$ raku ./common-path.raku
/a/b/c

Common Path in Perl

use strict;
use warnings;
use feature qw/say/;

my @input = qw <
    /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
    >;

my @paths;
for my $line (@input) {
    $line =~ s|^/||;
    my @subpaths = split /\//, $line;
    push @paths, [@subpaths[0..$#subpaths-1]];
}

my @first = @{$paths[0]};
my $end = $#paths;
my $k = 0;
OUTLOOP: for my $i (0..$#first) {
    for my $j (0..$end) {
        if ((not exists $paths[$j][$i]) or $paths[$j][$i] ne $first[$i]) {
            $k = $i - 1;
            last OUTLOOP;
        }
    }
}
say '/', join '/', @first[0..$k];

This program displays the following output:

$ perl ./common-path.pl
/a/b/c

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 25, 2022. 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.