This is my response to The Weekly Challenge #182.
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)
#! /usr/bin/env raku
unit sub MAIN (*@n where @n.elems > 0 && all(@n) ~~ Int); # [1]
my $max = @n.max; # [2]
for ^@n.elems -> $index # [3]
{
if @n[$index] == $max # [4]
{
say $index; # [5]
last; # [5a]
}
}
[1] Ensure that all the elements (of which there are one or more) are integers.
[2] Get the biggest number (with max
).
See
docs.raku.org/routine/max
for more information about max
.
[3] Iterate over the indices in the array.
[4] Do we have the highest number at this position in the array?
[5] If so, print the index and exit the loop [5b].
Running it:
$ ./max-index 5 2 9 1 7 6
2
$ ./max-index 4 2 3 1 5 0
4
Looking good.
max
).
Note that I have not implemented a check on input in the version; neither the type nor the number of them.
File: max-index-perl
#! /usr/bin/env perl
use strict;
use warnings;
use feature 'say';
my @n = @ARGV; # [1]
my $max = $n[0]; # [2]
my $index = 0; # [3]
for my $i (1 .. $#n) # [4]
{
if ($n[$i] > $max) # [5]
{
$max = $n[$i]; # [6]
$index = $i; # [6a]
}
}
say $index; # [7]
[1] Get (copy) the first value in the array.
[2] The maximum value, initialised to the first value.
[3] The index of the maximum value, also initialised to the first value.
[4] Iterate over the indices in the array, excluding the first one (as it has been done already).
[5] Is the new value higfher than the recorded maximum value?
[6] If so, update the maximum value and the index [6a].
[7] Print the index.
Running it gives the same result as the Raku version:
$ ./max-index-perl 5 2 9 1 7 6
2
$ ./max-index-perl 4 2 3 1 5 0
4
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
It does not really matter that this is file paths, right? They are just strings. Let us start programming before we have time to think about the soundness of this assumption...
File: common-path-first
#! /usr/bin/env raku
unit sub MAIN ($file where $file.IO.f && $file.IO.r = "paths.txt"); # [1]
my @paths = $file.IO.lines; # [2]
my $first = @paths.shift; # [3]
while @paths # [4]
{
my $next = @paths.shift; # [5]
$first = common($first, $next); # [6]
}
say $first; # [7]
sub common ($a, $b) # [6a]
{
my $length = min($a.chars, $b.chars); # [8]
for ^$length -> $index # [9]
{
return $a.substr(0, $index)
unless $a.substr($index,1) eq $b.substr($index,1); # [10]
}
return $a.substr(0, $length); # [11]
}
[1] Ensure that the filename is a file (.f
), and that we can
read it (.r
). Note the default value.
[2] Read the paths from the file.
[3] Get the first path.
[4] As long as there are paths left,
[5] Get the next one.
[6] Set the first one to the common value of the first and second.
[7] Print the result.
[8] Get the minimum length, i.e. the breakout point for the comparison.
See
docs.raku.org/routine/min
for more information about min
.
[9] Iterate over the indices, up to the breakout point.
[10] Return the string up to the previous character, if the current ones differ.
See
docs.raku.org/routine/substr
for more information about substr
.
[11] They do not differ (up to the breakout point). Return the string up to that point.
Running it:
$ ./common-path-first
/a/b/c/
Looking sort of good. The trailing /
should not be there, according
to the challenge.
But...
We have a bigger problem.
Let me show it with another set of paths:
File: paths2.txt
/a/b/ssssssss
/a/b/ssxxxxxxx.pl
/a/b/sssssss.pl
/a/b/sssss.pl
/a/b/sssssss.pl
Running the program with this set:
$ ./common-path-first paths2.txt
/a/b/ss
The trailing «ss» is part of the filename, and not the path. The challenge wanted the path.
This is actually easy to fix:
File: common-path (changes only)
$first ~~ /(.*)\//; say $0.Str; # [1]
[1] Up to the last /
. Note the .Str
to stringify the
result, which is a Match object. Match objects stringify with funny brackets,
e.g. 「/a/b/c」
.
Running it:
$ ./common-path
/a/b/c
$ ./common-path paths2.txt
/a/b
#! /usr/bin/env perl
use strict;
use warnings;
use feature 'say';
use File::Slurp;
use feature 'signatures';
no warnings 'experimental::signatures';
my $file = shift(@ARGV) || "paths.txt";
my @paths = read_file($file);
my $first = shift(@paths);
while (@paths)
{
my $next = shift(@paths);
$first = common($first, $next);
}
$first =~ /(.*)\//; say $1; # [1]
sub common ($a, $b)
{
my $length = length($a) < length($b) ? length($a) : length($b); # [2]
for my $index (0 .. $length -1)
{
return substr($a, 0, $index)
unless substr($a, $index, 1) eq substr($b, $index, 1);
}
return substr($a, 0, $length);
}
[1] The first match is $1
in Perl (as opposed to $0
in
Raku).
[2] I could have used the «max» routine from «List::Util» as I have done several times before, but doing it by hand is ok - as we only have two values.
Running it gives the same result as the Raku version:
$ ./common-path-perl
/a/b/c
$ ./common-path paths2.txt
/a/b
And that's it.