Perl Weekly Challenge 184: Sequence Number and Split Array

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

Spoiler Alert: This weekly challenge deadline is due in a couple of days from now (on Oct. 2, 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: Sequence Number

You are given list of strings in the format aa9999 i.e. first 2 characters can be anything ‘a-z’ followed by 4 digits ‘0-9’.

Write a script to replace the first two characters with sequence starting with ‘00’, ‘01’, ‘02’ etc.

Example 1

Input: @list = ( 'ab1234', 'cd5678', 'ef1342')
Output: ('001234', '015678', '021342')

Example 2

Input: @list = ( 'pq1122', 'rs3334')
Output: ('001122', '013334')

Sequence Number in Raku

The program is fairly straight forward. For various reasons, I found that using the subst method was slightly more efficient (code-wise) than using a regex substitution.

for <ab1234 cd5678 ef1342>, <pq1122 rs3334> -> @test {
    my $i = 0;
    my @out;
    for @test {
        push @out, .subst(/^<[a..z]>**2/, $i.fmt("%02d"));
        $i++;
    }
    say "@test[]  =>  @out[]";
}

This program displays the following output:

$ raku ./sequence-number.raku
ab1234 cd5678 ef1342  =>  001234 015678 021342
pq1122 rs3334  =>  001122 013334

Sequence Number in Perl

This is essentially a port to Perl of the Raku program above:

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

for my $test ([<ab1234 cd5678 ef1342>], [<pq1122 rs3334>]) {
    my $i = 0;
    my @out = @$test;
    for (@out) {
        my $count = sprintf("%02d", $i);
        s/^[a-z]{2}/$count/;
        $i++;
    }
    say "@$test  =>  @out";
}

This program displays the following output:

$ perl ./sequence-number.pl
ab1234 cd5678 ef1342  =>  001234 015678 021342
pq1122 rs3334  =>  001122 013334

Task 2: Split Array

You are given list of strings containing 0-9 and a-z separated by space only.

Write a script to split the data into two arrays, one for integers and one for alphabets only.

Example 1

Input: @list = ( 'a 1 2 b 0', '3 c 4 d')
Output: [[1,2,0], [3,4]] and [['a','b'], ['c','d']]

Example 2

Input: @list = ( '1 2', 'p q r', 's 3', '4 5 t')
Output: [[1,2], [3], [4,5]] and [['p','q','r'], ['s'], ['t']]

Split Array in Raku

Using grep to keep either letters or numerals:

for ('a 1 2 b 0', '3 c 4 d'), ('1 2', 'p q r', 's 3', '4 5 t') -> @test {
    my (@letters, @digits);
    for @test -> $item {
        append @letters, grep {  /<alpha>+/ }, $item.split(/\s+/);
        append @digits, grep { /\d+/ }, $item.split(/\s+/);
    }
    .say for @letters, @digits;
}

This program displays the following output:

$ raku ./split-array.raku
[a b c d]
[1 2 0 3 4]
[p q r s t]
[1 2 3 4 5]

Split Array in Perl

This is a port to Perl of the above Raku program:

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

for my $test (['a 1 2 b 0', '3 c 4 d'], ['1 2', 'p q r', 's 3', '4 5 t']) {
    my (@letters, @digits);
    for my $item (@$test) {
        push @letters, grep { /[a-zA-Z]+/ } split /\s+/, $item;
        push @digits, grep { /\d+/ } split /\s+/, $item;;
    }
    say  "@letters \n@digits";
}

This program displays the following output:

$ perl  ./split-array.pl
a b c d
1 2 0 3 4
p q r s t
1 2 3 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 October 9, 2022. And, please, also spread the word about the Perl Weekly Challenge if you can.

Perl Weekly Challenge 182: Unique Array and Date Difference

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

Task 1: Unique Array

You are given list of arrayrefs.

Write a script to remove the duplicate arrayrefs from the given list.

Example 1

Input: @list = ([1,2], [3,4], [5,6], [1,2])
Output: ([1,2], [3,4], [5,6])

Example 2

Input: @list = ([9,1], [3,7], [2,5], [2,5])
Output: ([9, 1], [3,7], [2,5])

Unique Array in Raku

The Raku solution is essentially a one-liner (more than one line because of the tests). We convert the sub-arrays into strings and use the unique built-in routine to remove duplicates.

for ([1,2], [3,4], [5,6], [1,2]), 
    ([9,1], [3,7], [2,5], [2,5]) -> @test {
    @test>>.map({"[$^a, $^b]"}).flat.unique.say;
}

This program displays the following output:

$ raku ./unique-arrays.raku
([1, 2] [3, 4] [5, 6])
([9, 1] [3, 7] [2, 5])

Unique Array in Perl

In Perl, we use the %unique hash to remove duplicates.

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

for my $test ( [[1,2], [3,4], [5,6], [1,2]], 
               [[9,1], [3,7], [2,5], [2,5]] ) {
    my %unique = map { $_ => 1 } map { "[@$_]"} @$test;
    say join ", ",  keys %unique;
}

Note that, since this is not requested in the task specification, we’re not trying to keep the order of the input. It would be easy to keep the input order with an additional array.

This program displays the following output:

$ perl ./unique-arrays.pl
[3 4], [5 6], [1 2]
[3 7], [2 5], [9 1]

Task 2: Date Difference

You are given two dates, $date1 and $date2 in the format YYYY-MM-DD.

Write a script to find the difference between the given dates in terms on years and days only.

Example 1

    Input: $date1 = '2019-02-10'
           $date2 = '2022-11-01'
    Output: 3 years 264 days

Example 2

    Input: $date1 = '2020-09-15'
           $date2 = '2022-03-29'
    Output: 1 year 195 days

Example 3

    Input: $date1 = '2019-12-31'
           $date2 = '2020-01-01'
    Output: 1 day

Example 4

    Input: $date1 = '2019-12-01'
           $date2 = '2019-12-31'
    Output: 30 days

Example 5

    Input: $date1 = '2019-12-31'
           $date2 = '2020-12-31'
    Output: 1 year

Example 6

    Input: $date1 = '2019-12-31'
           $date2 = '2021-12-31'
    Output: 2 years

Example 7

    Input: $date1 = '2020-09-15'
           $date2 = '2021-09-16'
    Output: 1 year 1 day

Example 8

    Input: $date1 = '2019-09-15'
           $date2 = '2021-09-16'
    Output: 2 years 1 day

Date Difference in Raku

for ('2019-02-10', '2022-11-01'), 
    ('2020-09-15', '2022-03-29'),
    ('2019-12-31', '2020-01-01'),
    ('2019-12-01', '2019-12-31'),
    ('2019-12-31', '2020-12-31'),
    ('2019-12-31', '2021-12-31'),
    ('2020-09-15', '2020-09-16'),
    ('2019-09-15', '2021-09-16') -> @test {
    my @dates = map {Date.new($_) }, sort @test;
    my $delta-y = @dates[1].year - @dates[0].year;
    my ($y, $m, $d) = @dates[0].year, @dates[0].month.fmt("%02d"), 
        @dates[0].day.fmt("%02d");
    $delta-y -= 1 if "$m$d" > join "", @dates[1].month.fmt("%02d"), 
        @dates[1].day.fmt("%02d");
    $y += $delta-y;
    my $new-date = Date.new("$y-$m-$d");
    my $delta-d = @dates[1] - $new-date;
    say "@dates[]: $delta-y year(s) {$delta-d.fmt("%3d")} day(s)";
}

This script displays the following output:

$ raku ./date-diff.raku
2019-02-10 2022-11-01: 3 year(s) 264 day(s)
2020-09-15 2022-03-29: 1 year(s) 195 day(s)
2019-12-31 2020-01-01: 0 year(s)   1 day(s)
2019-12-01 2019-12-31: 0 year(s)  30 day(s)
2019-12-31 2020-12-31: 1 year(s)   0 day(s)
2019-12-31 2021-12-31: 2 year(s)   0 day(s)
2020-09-15 2020-09-16: 0 year(s)   1 day(s)
2019-09-15 2021-09-16: 2 year(s)   1 day(s)

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 October 2, 2022. And, please, also spread the word about the Perl Weekly Challenge if you can.

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.

Perl Weekly Challenge 181: Sentence Order and Hot day

These are some answers to the Week 181 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. 11, 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: Sentence Order

You are given a paragraph.

Write a script to order each sentence alphanumerically and print the whole paragraph.

Example:

Input:
    All he could think about was how it would all end. There was
    still a bit of uncertainty in the equation, but the basics
    were there for anyone to see. No matter how much he tried to
    see the positive, it wasn't anywhere to be seen. The end was
    coming and it wasn't going to be pretty.

Ouput:
    about All all could end he how it think was would. a anyone
    basics bit but equation, for in of see still the the There
    there to uncertainty was were. anywhere be he how it matter
    much No positive, see seen the to to tried wasn't. and be
    coming end going it pretty The to was wasn't.

Splitting the paragraph into sentences and then splitting the sentences into words and reordering them in alphabetic order is order is the easiest part, outputting the result in lines with approximately equal lengths is slightly more difficult.

Sentence Order in Raku

We split the input into sentences, and then split each sentence into words, reorder the words into the alphabetic order of their lower-case equivalent, and store each word in the @words array. Once this is completed, we put each word in turn into a string ($line). Once the string reaches a predefined length, we print out the string and continue with a new empty string.

constant $MAX = 55;

my $paragraph = q:to/END/;
    All he could think about was how it would all end. There was
    still a bit of uncertainty in the equation, but the basics
    were there for anyone to see. No matter how much he tried to
    see the positive, it wasn't anywhere to be seen. The end was
    coming and it wasn't going to be pretty.
    END

my @words;
for $paragraph.split(/'.'/) -> $sentence {
    next if $sentence ~~ /^\s*$/;   # remove any empty line
    @words.append(sort { $_.lc }, $sentence.split(/\s/));
    @words[*-1] ~= ".";
}
my $line = "";
for @words -> $w {
    $line ~= "$w ";
    if $line.chars > $MAX {
        say $line;
        $line = "";
    }
}
say $line;

This program displays the following output:

$ raku ./sentence-order.raku
about All all could end he how it think was would.  a anyone
basics bit but equation, for in of see still the the There
there to uncertainty was were.  anywhere be he how it matter
much No positive, see seen the to to tried wasn't.  and
be coming end going it pretty The to was wasn't.

Sentence Order in Perl

We do essentially the same as in Raku: we split the input paragraph into sentences, and then split each sentence into words, reorder the words into the alphabetic order of their lower-case equivalent, and store each word in the @words array. Once this is completed, we put each word and a space in turn into a string ($line). Once the string reaches a predefined length, we print out the string and continue with a new empty string.

use strict;
use warnings;
use feature qw/say/;
use constant MAX => 55;

my $paragraph = <<'END';
All he could think about was how it would all end. There was
still a bit of uncertainty in the equation, but the basics
were there for anyone to see. No matter how much he tried to
see the positive, it wasn't anywhere to be seen. The end was
coming and it wasn't going to be pretty.
END

my @words;
for my $sentence (split /\./, $paragraph) {
    next if $sentence =~ /^\s*$/;   # remove any empty line
    push @words, (sort { lc $a cmp lc $b } split /\s+/, $sentence);
    $words[-1] .= ".";
}
my $line = "";
for my $w (@words) {
    $line .= "$w ";
    if (length $line > MAX) {
        say $line;
        $line = "";
    }
}
say $line;

This program displays the following output:

$ perl ./sentence-order.pl
about All all could end he how it think was would.  a anyone
basics bit but equation, for in of see still the the There
there to uncertainty was were.  anywhere be he how it matter
much No positive, see seen the to to tried wasn't.  and
be coming end going it pretty The to was wasn't.

Task 2: Hot Day

You are given file with daily temperature record in random order.

Write a script to find out days hotter than previous day.

Example:

Input File: (temperature.txt)

2022-08-01, 20
2022-08-09, 10
2022-08-03, 19
2022-08-06, 24
2022-08-05, 22
2022-08-10, 28
2022-08-07, 20
2022-08-04, 18
2022-08-08, 21
2022-08-02, 25

Output:
2022-08-02
2022-08-05
2022-08-06
2022-08-08
2022-08-10

For our tests, we’ll use the temperature.txt file with the data provided above in the task specification.

Hot Day in Raku

We read the input file and store the data into an array of arrays, @daily-temps. We then sort the array in ascending dates and output those where the temperature is more that the previous day temperature.

my @daily-temps;
for './temperature.txt'.IO.lines -> $line {
    push @daily-temps, split /','\s/, $line;
}
my $temp = Inf;
for @daily-temps.sort({$^a leg $^b}) -> @record {
    say @record[0] if @record[1] > $temp;
    $temp = @record[1];
}

This program displays the following output:

$ raku ./hot-day.raku
2022-08-02
2022-08-05
2022-08-06
2022-08-08
2022-08-10

Hot Day in Perl

Again, this a simple port to Perl of the Raku program above. Please refer to the previous section if you need any explanations.

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

my @daily_temps;
my $file_in = "./temperature.txt";
open my $IN, "<", $file_in or die "Cannot open $file_in $!";
while (my $line = <$IN>) {
    chomp $line;
    push @daily_temps, [ split /,\s/, $line ];
}
my $temp = 1e9;
for my $record (sort {$a->[0] cmp $b->[0]} @daily_temps) {
    say $record->[0] if $record->[1] > $temp;
    $temp = $record->[1];
}

This program displays the following output:

$ perl hot-day.pl
2022-08-02
2022-08-05
2022-08-06
2022-08-08
2022-08-10

Hot Day in Awk

It is possible to sort an array in awk, but, in the Unix (and awk) philosophy, it is better to use the Unix sort function to sort the input before it is passed through a pipe to the awk program, so that we don’t even need to use an array and can process each input line in turn and print the dates that satisfy the temperature condition. This leads to a very short and concise program.

# Run as: sort temperature.txt | awk -f hot-day.awk
BEGIN {
    temp = 1000
    FS = ", "
}
{
    if ($2 > temp) {
        print $1
    }
    temp = $1
}

Output:

$ sort ./temperature.txt | awk -f ./hot-day.awk
2022-08-02
2022-08-05
2022-08-06
2022-08-08
2022-08-10

Hot Day in Julia

fh = open("./temperature.txt", "r")
lines = readlines(fh)
sorted_lines = sort(lines)
temp = 100000
for line in sorted_lines
    (date, temp_str) = split(line, ", ")
    curr_temp = parse(Int64, temp_str)
    if curr_temp > temp
        println(date)
    end
    global temp = curr_temp
end

Output:

$ julia ./hot-day.jl
2022-08-02
2022-08-05
2022-08-06
2022-08-08
2022-08-10

Hot Day in Python

lines = []
file_in = "./temperature.txt"
fh = open(file_in, "r")
for line in fh:
    line.rstrip()
    lines.append(line)
lines.sort();
temp = 1e9
for line in lines:
    fields = line.split(',')
    cur_temp = int(fields[1].strip())
    if cur_temp > temp:
        print(fields[0])
    temp = cur_temp

Output:

$ python3 ./hot-day.py
2022-08-02
2022-08-05
2022-08-06
2022-08-08
2022-08-10

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 18, 2022. And, please, also spread the word about the Perl Weekly Challenge if you can.

Perl Weekly Challenge 180: First Unique Character and Trim List

These are some answers to the Week 180 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. 4, 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: First Unique Character

You are given a string, $s.

Write a script to find out the first unique character in the given string and print its index (0-based).

Example 1

Input: $s = "Perl Weekly Challenge"
Output: 0 as 'P' is the first unique character

Example 2

Input: $s = "Long Live Perl"
Output: 1 as 'o' is the first unique character

First Unique Character in Raku

This is a straight-forward, no-frills solution. I thought that I could write a better (or, at least, simpler) solution using features such as ArrayHash or Hash::Ordered, but this did not seem to simplify the syntax. Similarly, I initially wanted to use the built-in first routine, but it only made things more complicated. So, I ended up with a plain-vanilla solution using a hash (the %h letter histogram) and an array (the @let array) in parallel, and a hand-made loop to look for the first unique letter.

for "Perl Weekly Challenge", "Long Live Perl" -> $test {
    my @let = $test.comb;
    my %h;      # histogram of letters
    %h{$_}++ for @let;
    say "$test: $_" and last if %h{@let[$_]} == 1 for 0..@let.end;
}

This script displays the following output:

$ raku ./first_unique.raku
Perl Weekly Challenge: 0
Long Live Perl: 1

First Unique Character in Perl

The good thing of having used a plain-vanilla solution in Raku is that it can be ported directly to Perl (and other languages) with only small changes. We’re using a hash (the %h letter histogram) and an array (the @let array) in parallel, and a hand-made loop to look for the first unique letter.

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

for my $test ("Perl Weekly Challenge", "Long Live Perl") {
    my @let = split //, $test;
    my %h;
    $h{$_}++ for @let;
    for my $i (0..$#let) {
        say "$test: $i" and last if $h{$let[$i]} == 1;
    }
}

This script displays the following output:

$ perl ./first_unique.pl
Perl Weekly Challenge: 0
Long Live Perl: 1

First Unique Character in Other Languages

The following is a series of implementations of the First Unique Character task in 9 guest languages as follows:

  • D
  • Julia
  • Javascript
  • Kotlin
  • Lua
  • Python
  • Ring
  • Ruby
  • Scala

First Unique Character in D

import std.stdio;
import std.array;

void main() {
    string[] tests = [ "Perl Weekly Challenge", "Long Live Perl" ];
    foreach(test; tests) {
        int[string] histo;
        string[] chars = test.split("");
        foreach (ch; chars) {
            histo[ch]++;
        }
        for (int i = 0; i < chars.length; i++) {
            if (histo[chars[i]] == 1) {
                writeln(test, ": ", i);
                break;
            } 
        }
    }
}

Output:

Perl Weekly Challenge: 0
Long Live Perl: 1

First Unique Character in Julia

Note, if you did not guess it, that the only function converts a one-letter string into a char. Also remember that Julia arrays start at index 1. The task asks us to provide the 0-based index of the first unique character. This is unnatural in Julia, so we have to subtract 1 from the Julia index to get a 0-based index.

for test in [ "Perl Weekly Challenge", "Long Live Perl" ]
    histo = Dict()
    letters = split(test, "")
    for ch in test
        histo[ch] = if (haskey(histo, ch)) histo[ch]+1 else 1 end
    end
    for i in 1:length(letters)
        if (histo[only(letters[i])] == 1)
            println(test, ": ", i-1)
            break
        end
    end
end

Note that this construct:

histo[ch] = if (haskey(histo, ch)) histo[ch]+1 else 1 end

makes it possible to avoid the stylistically horrible if ... else five-code-line construct.

Output:

$ julia ./first_unique.jl
Perl Weekly Challenge: 0
Long Live Perl: 1

First Unique Character in Javascript

let tests = [ "Perl Weekly Challenge", "Long Live Perl" ]
for  (let i = 0; i < tests.length; i++) {
    test = tests[i]
    histo = new Map();
    chars = test.split("")
    chars.forEach( char => {
        histo.set(char, histo.has(char) ? histo.get(char) + 1 : 1)
    })
    for (let i = 0; i < chars.length; i++) {
        if (histo.get(chars[i]) == 1) {
            console.log(test, ": ", i)
            break
        }
    }
}

Output:

Perl Weekly Challenge :  0
Long Live Perl :  1

First Unique Character in Kotlin

fun main() {
    val tests  = arrayOf("Perl Weekly Challenge", "Long Live Perl")
    for (test in tests) {
        var his = mutableMapOf<Char,Int>() // letter histogram
        val chars = test.toCharArray()
        for (ch in chars) {
            his[ch] = if (his.containsKey(ch)) his[ch]!! + 1 else 1
        }
        for (i in 0..chars.size) {
            if (his[chars[i]] == 1) {
                println(test + ": " + i)
                break
            }
        }
    }
}

Output:

Perl Weekly Challenge: 0
Long Live Perl: 1

First Unique Character in Lua

for _, test in pairs{"Perl Weekly Challenge", "Long Live Perl"} do
    local histo = {} -- letter histotogram
    for ch in string.gmatch(test, ".") do
        -- histo[ch] = histo[ch] == nil and 1 or histo[ch] + 1
        histo[ch] = (histo[ch] or 0) + 1
    end
    i = 0
    for ch in string.gmatch(test, ".") do
        if histo[ch] == 1 then
            print(test, ": ", i)
            break
        end
        i = i + 1
    end
end

Note that we’re using here the or logical operator:

histo[ch] = (histo[ch] or 0) + 1

to avoid this stylistically horrible code where histo[ch] is repeated no less that four times:

if histo[ch] == nil then
    histo[ch] = 1
else
    histo[ch] = histo[ch] + 1
end

This works because in Lua, the or logical operator doesn’t return a Boolean value, but the value of the first expression if it evaluates to true, and the value of the second one otherwise.

Output:

Perl Weekly Challenge   :   0
Long Live Perl  :   1

First Unique Character in Python

"""Find the first unique character in the given string"""
for test in ["Perl Weekly Challenge", "Long Live Perl"]:
    histo = dict()
    for char in test:
        histo[char] = histo.get(char, 0) + 1
    for i in range(0, len(test)):
        if histo[test[i]] == 1:
            print(test, ": ", i)
            break

Note that using the histo.get(char, 0) expression provides a default 0 value when the key doesn’t exists, thereby avoiding an if ... else construct.

Output:

$ python3 ./first_unique.py
Perl Weekly Challenge :  0
Long Live Perl :  1

First Unique Character in Ring

for test in [ "Perl Weekly Challenge", "Long Live Perl" ]
    histo = []
    for i = 1 to len(test)
        histo[test[i]] = 0 + histo[test[i]] + 1
    next
    for i = 1 to len(test)
        if histo[test[i]] = 1
            see test + ": " + (i-1) + nl
            exit
        ok
    next
next

Note that a 0-based index is unnatural in Ring, so we have to subtract 1 from the looping variable i.

Output:

$ ring ./first_unique.ring
Perl Weekly Challenge: 0
Long Live Perl: 1

First Unique Character in Ruby

for test in ["Perl Weekly Challenge", "Long Live Perl"]
    chars = test.split("")
    # with new(0), 0 becomes the default value when a key is absent
    histo = Hash.new(0)
    for char in chars
        histo[char] += 1
    end
    for i in 0..chars.length - 1
        if histo[chars[i]] == 1
            print test, ": ", i, "\n"
            break
        end
    end
end

Here again, using the 0 parameter in the call to the constructor (Hash.new(0)) tells Ruby to return a 0 default value when the key is absent.

Output:

Perl Weekly Challenge: 0
Long Live Perl: 1

First Unique Character in Scala

object firstUnique extends App {
  import scala.collection.mutable.Map
  val tests: List[String] =
    List("Perl Weekly Challenge", "Long Live Perl")
  for (test <- tests) {
    val chars = test.split("")
    var histo: Map[String, Int] = Map()
    for (ch <- chars) {
      if (histo.contains(ch)) {
        histo(ch) = histo(ch) + 1
      } else {
        histo += (ch -> 1)
      }
    }
    var continue = true
    var i = 0
    while (continue) {
      if (histo(chars(i)) == 1) {
        println(test + ": " + i)
        continue = false

      }
      i = i + 1
    }
  }
}

Output:

Perl Weekly Challenge: 0
Long Live Perl: 1

Task 2: Trim List

You are given list of numbers, @n and an integer $i.

Write a script to trim the given list where element is less than or equal to the given integer.

Example 1

Input: @n = (1,4,2,3,5) and $i = 3
Output: (4,5)

Example 2

Input: @n = (9,0,6,2,3,8,5) and $i = 4
Output: (9,6,8,5)

This is quite easy using the built-in grep function in Raku and Perl (or filter in Python or Scala).

Trim List in Raku

Rather than filtering out items which are less than or equal to the input integer, we keep numbers that are larger than the input integer.

for [3, [1,4,2,3,5]], [4, [9,0,6,2,3,8,5]] -> $test {
    my $i = $test[0];
    my @nums = |$test[1];
    say "i = $i; nums = @nums[] => ", grep { $_ > $i }, @nums;
}

This script displays the following output:

$ raku ./trim_list.raku
i = 3; nums = 1 4 2 3 5 => (4 5)
i = 4; nums = 9 0 6 2 3 8 5 => (9 6 8 5)

Trim List in Perl

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

for my $test ( [3, [1,4,2,3,5]], [4, [9,0,6,2,3,8,5]] ) {
    my $i = $test->[0];
    my @nums = @{$test->[1]};
    say "i = $i, nums = @nums => ", join " ", grep $_ > $i, @nums;
}

This script displays the following output:

$ perl ./trim_list.pl
i = 3, nums = 1 4 2 3 5 => 4 5
i = 4, nums = 9 0 6 2 3 8 5 => 9 6 8 5

Trim List in Other Languages

The following is a series of implementations of the Trim List task in 14 guest languages as follows:

  • in Awk
  • in C
  • in D
  • in Java
  • in JavaScript
  • in Julia
  • in Go
  • in Kotlin
  • in Lua
  • in Pascal
  • in Python
  • in Ring
  • in Ruby
  • in Scala

Trim List in Awk

Contrary to Raku and Perl, awk doesn’t have a grep or filter feature. So we implement a loop over the values and print out those that satisfy the condition. We’ll do that in several other languages. Also note that, since awk is essentially driven by external data, the data is passed by the shell to the awk script. On each line, the first item in the input integer to be compared the other values.

# run for example as:
# echo '3 1 4 2 3 5 
#      4 9 0 6 2 3 85 ' | awk -f trim_list.awk

{ 
    i = $1
    printf "Input = %-18s- i = %d   => result = ", $0, $1
    for (j = 2; j <= NF; j++) {
        if ( $j > i) {
            printf "%d ", $j
        }
    }
    print ""
}

Output:

$ echo '3 1 4 2 3 5
4 9 0 6 2 3 8 5 ' | awk -f trim_list.awk
Input = 3 1 4 2 3 5       - i = 3   => result = 4 5
Input = 4 9 0 6 2 3 8 5   - i = 4   => result = 9 6 8 5

Trim List in C

C doesn’t have a built-in grep or filter feature. So we implement a loop over the values and print out those that satisfy the condition. On each test line, the first item in the input integer to be compared the other values.

#include <stdio.h>
#include <string.h>

int main() {
    const char tests[2][10] = {
            { 3, 1, 4, 2, 3, 5 },
            { 4, 9, 0, 6, 2, 3, 8, 5 }
        };
    for (int j = 0; j <=1; j++) {
        char test[10];
        memcpy(test, tests[j],  sizeof(tests[j]));
        int i = test[0];
        printf("i = %d; nums = ", i);
        /* printing input test array */
        for (char k = 1; k < sizeof(test); k++) {
            printf("%d ", test[k]);
        }
        printf("  =>  ");
        /* printing the result */
        for (char k = 1; k < sizeof(test); k++) {
            if (test[k] > i) {
                printf("%d ", test[k]);
            }
        }
        printf("%s\n", "");
    }
}

Output:

$ ./a.out
i = 3; nums = 1 4 2 3 5 0 0 0 0   =>  4 5
i = 4; nums = 9 0 6 2 3 8 5 0 0   =>  9 6 8 5

Trim List in D

import std.stdio;

void main() {
    int[][][] tests = [[[3], [1,4,2,3,5]], [[4], [9,0,6,2,3,8,5]]];
    foreach(test; tests) {
        int i = test[0][0];
        int[] nums = test[1];
        write(i, " ", nums, "  =>  ");
        for (int j = 0; j < nums.length; j++) {
            if (nums[j] > i) {
                printf("%d ", nums[j]);
            }
        }
    writeln("");
    }
}

Output:

3 [1, 4, 2, 3, 5]  =>  4 5 
4 [9, 0, 6, 2, 3, 8, 5]  =>  9 6 8 5

Trim List in Java

import java.util.Arrays;

public class TrimList {
    private static final int[][][] tests =
        {{{3}, {1,4,2,3,5}}, {{4}, {9,0,6,2,3,8,5}}};
    public static void main(String[] args) {
        for (int j = 0; j < tests.length; j++) {
            int i = tests[j][0][0];
            int[] nums = tests[j][1];
            System.out.printf("i = %d; nums = %s => ", 
                i, Arrays.toString(nums));
            for (int k = 0; k < nums.length; k++) {
                if (nums[k] > i) {
                    System.out.printf("%d ", nums[k]);
                }
            }
        System.out.printf("%s", "\n");
        }
    }
}

Output:

i = 3; nums = [1, 4, 2, 3, 5] => 4 5 
i = 4; nums = [9, 0, 6, 2, 3, 8, 5] => 9 6 8 5

Trim List in JavaScript

tests = [[[3], [1,4,2,3,5]], [[4], [9,0,6,2,3,8,5]]];
for(let j = 0; j < tests.length; j++) {
    let i = tests[j][0][0]
    let nums = tests[j][1]
    process.stdout.write("i= " + i + " nums= " + nums + "  =>  ")
    for (let k = 0; k < nums.length; k++) {
        if ( nums[k] > i) {
            process.stdout.write(nums[k] + " ")
        }
    }
    process.stdout.write("\n")
}

Output:

i= 3 nums= 1,4,2,3,5  =>  4 5 
i= 4 nums= 9,0,6,2,3,8,5  =>  9 6 8 5

Trim List in Julia

In my humble opinion, Julia is the best language used in this blog post after Raku and Perl. Just consider how concise and clear this implementation is, compared to most other languages. And it has very nice functional programming capabilities (e.g. the filter built-in function below with its lambda). In addition, Julia runs fast. Its only drawback in my view is that the error messages emitted by the compiler or the runtime are really not clear (like most languages that compile to Java code).

for test in [[3, [1,4,2,3,5]], [4, [9,0,6,2,3,8,5]]]
    i = test[1]
    nums = test[2]
    println( "i = $i, num = $nums => ", filter((x) -> x > i, nums))
end

Output:

$ julia ./trim_list.jl
i = 3, num = [1, 4, 2, 3, 5]: [4, 5]
i = 4, num = [9, 0, 6, 2, 3, 8, 5]: [9, 6, 8, 5]

Trim List in Go

package main

import "fmt"

func main() {
    tests := [2][2][8]int{{{3}, {1, 4, 2, 3, 5}}, 
                          {{4}, {9, 0, 6, 2, 3, 8, 5}}
                         }
    for j := 0; j < len(tests); j++ {
        i := tests[j][0][0]
        fmt.Printf("i = %d; nums = ", i)
        nums := tests[j][1]
        fmt.Print(nums, "; => ")
        for k := 0; k < len(nums); k++ {
            if nums[k] > i {
                fmt.Printf("%d ", nums[k])
            }
        }
        fmt.Println("")
    }
}

Output:

i = 3; nums = [1 4 2 3 5 0 0 0]; => 4 5 
i = 4; nums = [9 0 6 2 3 8 5 0]; => 9 6 8 5

Trim List in Kotlin

Declaring a multidimensional array in Kotlin is a pain in the neck (as well as in Scala, and it is even worse in Nim). Besides that, the implementation is quite straight forward.

import java.util.Arrays

fun main() {
    val tests  = arrayOf(arrayOf(intArrayOf(3,), 
                                 intArrayOf(1,4,2,3,5)),
                         arrayOf(intArrayOf(4,), 
                                 intArrayOf(9,0,6,2,3,8,5))
                        )

    for (test in tests) {
        print(Arrays.deepToString(test) + "  =>  ")
        var i = test[0][0]
        var nums = test[1];
        for (j in nums) {
            if (j > i) {
                print(j.toString() + " ")
            }
        }
        println("")

    }
}

Output:

[[3], [1, 4, 2, 3, 5]]  =>  4 5 
[[4], [9, 0, 6, 2, 3, 8, 5]]  =>  9 6 8 5

Trim List in Lua

tests = {{{3}, {1,4,2,3,5}}, {{4}, {9,0,6,2,3,8,5}}}
for _, test in ipairs(tests) do
    local i = test[1][1]
    local nums = test[2]
    io.write("i= ", i, "; nums= ", table.concat(nums, " "), " => ")
    for _, j in ipairs(nums) do
        if j > i then
            io.write(j, " ")
        end
    end
    print("")
end

Output:

i= 3; nums= 1 4 2 3 5 => 4 5 
i= 4; nums= 9 0 6 2 3 8 5 => 9 6 8 5

Trim List in Pascal

In Pascal, since I declared the third dimension of the tests array to be 0..6, I had to fill the empty slots with zeroes. If there is a better way, I do not know it (I learned and used Pascal about 33 years ago and almost did not use it since, please forgive my ignorance). It not, then it is a major weakness of the language. Otherwise, the syntax is a bit verbose, but very clear.

program TrimList;

const tests : array[0..1,0..1,0..6] of integer =
    (((3,0,0,0,0,0,0), (1,4,2,3,5,0,0)), ((4,0,0,0,0,0,0), (9,0,6,2,3,8,5)));

var
    i, j, k: integer;
    nums: array of integer;

begin
    for j := 0 to length(tests) - 1 do
    begin
        i := tests[j][0][0];
        write('i = ', i, '; nums = ');
        nums := tests[j][1];
        for k := 0 to length(nums) - 1 do
        begin
            write(nums[k], ' ');
        end;
        write(' => ');
        for k := 0 to length(nums) - 1 do
        begin
            if nums[k] > i then
                write(nums[k], ' ');
        end;
        writeln('')
    end;
end.

Output:

i = 3; nums = 1 4 2 3 5 0 0  => 4 5 
i = 4; nums = 9 0 6 2 3 8 5  => 9 6 8 5

Trim List in Python

About 19 years ago, I took up Perl and stopped writing in Python (my favorite language for a few years at that point), because I felt Perl was superior. I still think so, but I must admit Python is fairly expressive and concise.

for test in [3, [1,4,2,3,5]], [4, [9,0,6,2,3,8,5]]:
  i = test[0]
  nums = test[1]
  print(i, nums, " => ", list(filter(lambda n: n>i, nums)))

Output:

$ python3 ./trim_list.py
3 [1, 4, 2, 3, 5]  =>  [4, 5]
4 [9, 0, 6, 2, 3, 8, 5]  =>  [9, 6, 8, 5]

Trim List in Ring

for test in [[3, [1,4,2,3,5]], [4, [9,0,6,2,3,8,5]]]
    i = test[1]
    nums = test[2]
    see "i = " + i + " ; nums = " 
    for j in nums
        see "" + j + " "
    next
    see " => "
    for j in nums
        if j > i
            see "" + j + " "
        ok
    next
    see " " +nl
next

Output:

$ ring  ./trim_list.ring
i = 3 ; nums = 1 4 2 3 5  => 4 5
i = 4 ; nums = 9 0 6 2 3 8 5  => 9 6 8 5

Trim List in Ruby

for test in [ [3, [1,4,2,3,5]], [4, [9,0,6,2,3,8,5]] ]
    i = test[0]
    nums = test[1]
    print "i = #{i}, nums = #{nums} => ", nums.select {|n| n > i}
    puts " "
end

Output:

i = 3, nums = [1, 4, 2, 3, 5] => [4, 5] 
i = 4, nums = [9, 0, 6, 2, 3, 8, 5] => [9, 6, 8, 5]

Trim List in Scala

As I said above, declaring multidimensional arrays is a pain in the neck in Scala. And its typing system can be a straitjacket. It’s a pity because Scala has very nice features in terms of combining object-oriented and functional programming paradigms.

object trimList extends App {
  val tests: List[List[List[Int]]] = List(
    List(List(3), List(1, 4, 2, 3, 5)),
    List(List(4), List(9, 0, 6, 2, 3, 8, 5))
  )
  for (test <- tests) {
    val i = test(0)(0)
    val nums = test(1)
    print(s"i: $i, nums: " + nums.mkString(" "))
    println(" => " + nums.filter(_ > i).mkString(" "))
  }
}

Output:

i: 3, nums: 1 4 2 3 5 => 4 5
i: 4, nums: 9 0 6 2 3 8 5 => 9 6 8 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 11, 2022. And, please, also spread the word about the Perl Weekly Challenge if you can.