October 2022 Archives

Perl Weekly Challenge 188: Divisible Pairs and Total Zero

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

Task 1: Divisible Pairs

You are given list of integers @list of size $n and divisor $k.

Write a script to find out count of pairs in the given list that satisfies the following rules.

The pair (i, j) is eligible if and only if
a) 0 <= i < j < len(list)
b) list[i] + list[j] is divisible by k

Example 1

Input: @list = (4, 5, 1, 6), $k = 2
Output: 2

Example 2

Input: @list = (1, 2, 3, 4), $k = 2
Output: 2

Example 3

Input: @list = (1, 3, 4, 5), $k = 3
Output: 2

Example 4

Input: @list = (5, 1, 2, 3), $k = 4
Output: 2

Example 5

Input: @list = (7, 2, 4, 5), $k = 4
Output: 1

Divisible Pairs in Raku

We use 2-item combinations of indice between 0 and the index of the last item of the list to satisfy rule (a). Then we increment $count if the sum of the two items is divisible by the input divisor.

for (2, <4 5 1 6>), (2, <1 2 3 4>),
    (3, <1 3 4 5>), (4, <5 1 2 3>),
    (4, <7 2 4 5>), (2, < 1 2 3 4 5 6 7 >)
        -> ($k, @test) {
    my $count = 0;
    for (0..@test.end).combinations(2) -> @comb {
        $count++ if (@test[@comb[0]] + @test[@comb[1]]) %% $k;
    }
    say "$k  (@test[])  -> ", $count;
}

This script displays the following output:

$ raku ./divisible-pairs.raku
2  (4 5 1 6)  -> 2
2  (1 2 3 4)  -> 2
3  (1 3 4 5)  -> 2
4  (5 1 2 3)  -> 2
4  (7 2 4 5)  -> 1
2  (1 2 3 4 5 6 7)  -> 9

Divisible Pairs in Perl

This is essentially the same approach as the Raku program above, except that we generate the combinations with two nested for loops.

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

for my $test ([2, [<4 5 1 6>]], [2, [<1 2 3 4>]],
    [3, [<1 3 4 5>]], [4, [<5 1 2 3>]],
    [4, [<7 2 4 5>]], [2, [< 1 2 3 4 5 6 7 >]]) {
    my $k = $test->[0];
    my @list = @{$test->[1]};
    my $count = 0;
    for my $i (0..$#list) {
        for my $j (($i+1) .. $#list) {
            ++$count if ($list[$i] + $list[$j]) % $k == 0;
        }
    }   
    say "$k  (@list)  -> ", $count;
}

This script displays the following output:

$ perl  ./divisible-pairs.pl
2  (4 5 1 6)  -> 2
2  (1 2 3 4)  -> 2
3  (1 3 4 5)  -> 2
4  (5 1 2 3)  -> 2
4  (7 2 4 5)  -> 1
2  (1 2 3 4 5 6 7)  -> 9

Task 2: Total Zero

You are given two positive integers $x and $y.

Write a script to find out the number of operations needed to make both ZERO. Each operation is made up either of the followings:

$x = $x - $y if $x >= $y

or

$y = $y - $x if $y >= $x (using the original value of $x)

Example 1

Input: $x = 5, $y = 4
Output: 5

Example 2

Input: $x = 4, $y = 6
Output: 3

Example 3

Input: $x = 2, $y = 5
Output: 4

Example 4

Input: $x = 3, $y = 1
Output: 3

Example 5

Input: $x = 7, $y = 4
Output: 5

This problem could certainly be solved with simple mathematical analysis, but I suspect we might end up with enough edge cases to make the program more complicated than a simple brute-force approach, i.e. iteratively computing the successive values of $x and $y.

Total Zero in Raku

sub to-zero ($x, $y) {
    return $x >= $y ?? ($x - $y, $y) !! ($x, $y - $x);
}

for <5 4>, <4 6>, <2 5>, <3 1>, <7 4>, <9 1> -> @test {
    my ($x, $y) = @test;
    my $count = 0;
    while ($x and $y ) {
        ($x, $y) = to-zero $x, $y;
        $count++;
    }
    say "@test[] -> $count";
}

This script displays the following output:

$ raku ./total-zero.raku
5 4 -> 5
4 6 -> 3
2 5 -> 4
3 1 -> 3
7 4 -> 5
9 1 -> 9

Total Zero in Perl

This a port to Perl of the Raku program above.

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

sub to_zero  {
    my ($x, $y) = @_;
    return $x >= $y ? ($x - $y, $y) : ($x, $y - $x);
}

for my $test ([5, 4], [4, 6], [2, 5], [3, 1], [7, 4], [9, 1]) {
    my ($x, $y) = @$test;
    my $count = 0;
    while ($x and $y ) {
        ($x, $y) = to_zero $x, $y;
        $count++;
    }
    say "@$test -> $count";
}

This script displays the following output:

$ perl ./total-zero.pl
5 4 -> 5
4 6 -> 3
2 5 -> 4
3 1 -> 3
7 4 -> 5
9 1 -> 9

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

Perl Weekly Challenge 187: Days Together and Magical Triplets

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

Task 1: Days Together

Two friends, Foo and Bar gone on holidays separately to the same city. You are given their schedule i.e. start date and end date.

To keep the task simple, the date is in the form DD-MM and all dates belong to the same calendar year i.e. between 01-01 and 31-12. Also the year is non-leap year and both dates are inclusive.

Write a script to find out for the given schedule, how many days they spent together in the city, if at all.

Example 1

Input: Foo => SD: '12-01' ED: '20-01'
       Bar => SD: '15-01' ED: '18-01'

Output: 4 days

Example 2

Input: Foo => SD: '02-03' ED: '12-03'
       Bar => SD: '13-03' ED: '14-03'

Output: 0 day

Example 3

Input: Foo => SD: '02-03' ED: '12-03'
       Bar => SD: '11-03' ED: '15-03'

Output: 2 days

Example 4

Input: Foo => SD: '30-03' ED: '05-04'
       Bar => SD: '28-03' ED: '02-04'

Output: 4 days

Days Together in Raku

The idea here is to create a yearly calendar mapping dates such as ‘05-04’ to their day number in the year. It is then quite easy to use simple arithmetic subtraction to find the result.

my @m = < 0 31 28 31 30 31 30 31 31 30 31 30 31 >;
my $c = 1;
my %dates = map { $_ => $c++ }, 
            (map { ((1..@m[$_])>>.fmt("%02d-$_")) }, 
            map {.fmt("%02d")}, 1..12).flat;

sub compute-common ($sd1, $ed1, $sd2, $ed2) {
    my $start-common = max %dates{$sd1}, %dates{$sd2};
    my $end-common = min %dates{$ed1}, %dates{$ed2};
    return $end-common - $start-common + 1;
}

for <12-01 20-01 15-01 18-01>, <03-03 12-03 13-03 14-03>,
    <02-03 12-03 11-03 15-03>, <30-03 05-04 28-03 02-04>,
    <12-01 13-03 09-01 25-04>
        -> @input-dates {
    say "Number of days together for dates @input-dates[]: ", compute-common | @input-dates;
}

This program displays the following output:

$ raku ./days-together.raku
Number of days together for dates 12-01 20-01 15-01 18-01: 4
Number of days together for dates 03-03 12-03 13-03 14-03: 0
Number of days together for dates 02-03 12-03 11-03 15-03: 2
Number of days together for dates 30-03 05-04 28-03 02-04: 4
Number of days together for dates 12-01 13-03 09-01 25-04: 61

Days Together in Perl

This is port to Perl of the Raku program above, using the same idea of creating first a yearly calendar to make date computations easy.

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

my @months = < 0 31 28 31 30 31 30 31 31 30 31 30 31 >;
my @dates;
for my $m (map {sprintf( "%02d", $_ ) } 1..12) {
    push @dates, map {sprintf ( "%02d-$m", $_ )} 1..$months[$m];
}
my $c = 1;
my %d = map { $_ => $c++ } @dates;

sub compute_common {
    my ($sd1, $ed1, $sd2, $ed2) = map $d{$_}, @_;
    my $start_common = $sd1 > $sd2 ? $sd1 : $sd2;
    my $end_common   = $ed1 < $ed2 ? $ed1 : $ed2;
    return $end_common - $start_common + 1;
}

for my $test (
    [<12-01 20-01 15-01 18-01>], [<03-03 12-03 13-03 14-03>],
    [<02-03 12-03 11-03 15-03>], [<30-03 05-04 28-03 02-04>],
    [<12-01 13-03 09-01 25-04>] ) {
        say "Number of days together for dates @$test]: ",
            compute_common @$test;
}

This program displays the following output:

$ perl ./days-together.pl
Number of days together for dates 12-01 20-01 15-01 18-01]: 4
Number of days together for dates 03-03 12-03 13-03 14-03]: 0
Number of days together for dates 02-03 12-03 11-03 15-03]: 2
Number of days together for dates 30-03 05-04 28-03 02-04]: 4
Number of days together for dates 12-01 13-03 09-01 25-04]: 61

Task 2: Magical Triplets

You are given a list of positive numbers, @n, having at least 3 numbers.

Write a script to find the triplets (a, b, c) from the given list that satisfies the following rules.

1. a + b > c
2. b + c > a
3. a + c > b
4. a + b + c is maximum.

In case, you end up with more than one triplets having the maximum then pick the triplet where a >= b >= c.

Example 1

Input: @n = (1, 2, 3, 2);
Output: (3, 2, 2)

Example 2

Input: @n = (1, 3, 2);
Output: ()

Example 3

Input: @n = (1, 1, 2, 3);
Output: ()

Example 4

Input: @n = (2, 4, 3);
Output: (4, 3, 2)

In essence, the rules for a triplet to be valid are the same as those to determine whether three segments can form a triangle. But that doesn’t really help solving the problem.

Although the task specification says “to find the triplets”, it appears from the examples that what is required is only one triplet, the “maximum” one, i.e. the one with the maximum sum. To achieve that, I’ll simply sort the input list in descending order so that the the first triplet found will be the “maximum” one. In my humble view, the second rule to find the best triplet doesn’t really make sense (there may be several triplets matching that second rule, and this is what would happen in fact with the first example if the maximum rule did not apply first), so I’ll simply skip it.

Magical Triplets in Raku

The is-valid-triplet subroutine checks that size rules are satisfied. As mentioned above, we start with sorting in descending order the input list to make sure that the first triplet found is the maximum one (we could actually stop the for loop after having found the first triplet but didn’t do it because it would make sense to modify the program at some time in the future to list all valid triplets).

sub is-valid-triplet ($a, $b, $c) {
    return False if $c >= $a + $b;
    return False if $b >= $a + $c;
    return False if $a >= $b + $c;
    return True;
}
for <1 2 3 2>, <1 3 2>, <1 1 2 3>, <2 4 3> -> @test {
    my @valid;
    for @test.sort.reverse.combinations: 3 -> @triplet {
        push @valid, @triplet if is-valid-triplet | @triplet;
    }
    say @test, " => ", @valid.elems > 0 ?? @valid[0] !! "()";
}

This program displays the following output:

$ raku ./magical-triplet.raku
(1 2 3 2) => (3 2 2)
(1 3 2) => ()
(1 1 2 3) => ()
(2 4 3) => (4 3 2)

Magical Triplets in Perl

This is a port to Perl of the Raku program above, and the comments made above also apply here. Since there is no built-in combinations routine in Perl, we roll out our own recursive combine subroutine.

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

my @result;

sub is_valid_triplet {
    my ($a, $b, $c) = @_;
    return 0 if $c >= $a + $b;
    return 0 if $b >= $a + $c;
    return 0 if $a >= $b + $c;
    return 1;
}

sub combine {
    my @out = @{$_[0]};
    my @in  = @{$_[1]};
    if (@out == 3) {
        push @result, $_[0] if is_valid_triplet @out;
        return;
    }
    for my $i (0..$#in) {
        combine ([ @out, $in[$i] ], [ @in[$i+1..$#in] ]);
    }
}

for my $test ( [<1 2 3 2>], [<1 3 2>], [<1 1 2 3>], [<2 4 3> ]) {    
    @result = ();
    combine ([], [ sort { $b <=> $a } @$test]);
    say "@$test => ", @result > 0 ? "@{$result[0]}" : "()";
}

This program displays the following output:

$ perl ./magical-triplet.pl
1 2 3 2 => 3 2 2
1 3 2 => ()
1 1 2 3 => ()
2 4 3 => 4 3 2

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

Perl Weekly Challenge 186: Zip List and Unicode Makeover

These are some answers to the Week 186 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 Oct. 16, 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: Zip List

You are given two lists @a and @b of same size.

Create a subroutine sub zip(@a, @b) that merges the two lists as shown in the example below.

Example:

Input:  @a = qw/1 2 3/; @b = qw/a b c/;
Output: zip(@a, @b) should return qw/1 a 2 b 3 c/;
        zip(@b, @a) should return qw/a 1 b 2 c 3/;

Zip List in Raku

Raku has a built-in zip routine, so we will name zip-it our subroutine to avoid any confusion. In addition, Raku has an infix Z operator which performs exactly what is requested in the task. So we will use this operator in thezip-it subroutine.

sub zip-it (@a, @b) {
    return ~ (@a Z @b).flat;
}
my @tests = <1 2 3>, <a b c>;
say zip-it @tests[0], @tests[1];
say zip-it @tests[1], @tests[0];

This script displays the following output:

$ raku ./zip-list.raku
1 a 2 b 3 c
a 1 b 2 c 3

Zip List in Perl

The program iterates over the indices of any of the two lists (which have the same size) and concatenates to the output the corresponding values of both arrays.

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

sub zip  {
    my @c = @{$_[0]};
    my @d = @{$_[1]};
    my $out = "";
    for my $i (0..$#c) {
        $out = $out . $c[$i] . " " . $d[$i] . " " ;
    }
    return $out;
}
my @tests = ([<1 2 3>], [<a b c>]);
say zip $tests[0], $tests[1];
say zip $tests[1], $tests[0];

This script displays the following output:

$ perl ./zip-list.pl
1 a 2 b 3 c
a 1 b 2 c 3

Task 2: Unicode Makeover

You are given a string with possible unicode characters.

Create a subroutine sub makeover($str) that replace the unicode characters with ascii equivalent. For this task, let us assume it only contains alphabets.

Example 1:

Input: $str = 'ÃÊÍÒÙ';
Output: 'AEIOU'

Example 2:

Input: $str = 'âÊíÒÙ';
Output: 'aEiOU'

I’m not sure what is meant by “it only contains alphabets,” but the two examples provided only contain vowels in the right alphabetical order. In my implementations, I’ve used the two test cases provided above and added a third test case, just for testing a few more letters, without attempting to satisfy any particular order.

I don’t like very much problems dealing with Unicode because, while I know quite a few things about Unicode, UTF8, and so on, I usually don’t fully understand what is going on at a deeper level. As a result, I often end up trying various things until it works properly, and this is really not my vision of what a programmer should be doing.

Unicode Makeover in Raku

In Raku, we’ll use the built-in samemark routine, which does exactly what we need.

sub makeover ($in) {
    return $in.samemark('a');
}
for 'ÃÊÍÒÙ', 'âÊíÒÙ', 'àçùòîéèûä' -> $test {
    say "$test -> \t", makeover($test);
}

This script displays the following output:

$ raku ./unicode_makeover.raku
ÃÊÍÒÙ ->        AEIOU
âÊíÒÙ ->        aEiOU
àçùòîéèûä ->    acuoieeua

Unicode Makeover in Perl

use strict;
use warnings;
use feature 'say';
use utf8;
use Unicode::Normalize;
binmode(STDOUT, ":utf8");

sub makeover {
    return join '', map { /(.)/ } map { /(\X)/g } NFD shift;
}

for my $test ('ÃÊÍÒÙ', 'âÊíÒÙ', 'àçùòîéèûä' ) {
    say "$test -> \t", makeover($test);
}

This script displays the following output:

$ perl ./unicode_makeover.pl
ÃÊÍÒÙ ->        AEIOU
âÊíÒÙ ->        aEiOU
àçùòîéèûä ->    acuoieeua

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

Perl Weekly Challenge 185: MAC Address and Mask Code

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

Task 1: MAC Address

You are given MAC address in the form i.e. hhhh.hhhh.hhhh.

Write a script to convert the address in the form hh:hh:hh:hh:hh:hh.

Example 1:

Input:  1ac2.34f0.b1c2
Output: 1a:c2:34:f0:b1:c2

Example 2:

Input:  abc1.20f1.345a
Output: ab:c1:20:f1:34:5a

MAC Address in Raku

This is done in a hurry, less than 45 minutes before the deadline. There might be a better or simpler way to solve this task, but using a couple of regexes is so simple that I don’t see any reason to try something else.

for "1ac2.34f0.b1c2", "abc1.20f1.345a" -> $test {
    $_ = $test;
    s:g/\./:/;
    s:g/(\w\w)(\w\w)/$0:$1/;
    .say;
}

This script displays the following output:

$ raku ./mac-address.raku
1a:c2:34:f0:b1:c2
ab:c1:20:f1:34:5a

MAC Address in Perl

Also using two regexes in Perl:

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

for my $test ("1ac2.34f0.b1c2", "abc1.20f1.345a") {
    $_ = $test;
    s/\./:/g;
    s/(\w\w)(\w\w)/$1:$2/g;
    say;
}

This script displays the following output:

$ perl ./mac-address.pl
1a:c2:34:f0:b1:c2
ab:c1:20:f1:34:5a

Task 2: Mask Code

You are given a list of codes in many random format.

Write a script to mask first four characters (a-z,0-9) and keep the rest as it is.

Example 1

Input: @list = ('ab-cde-123', '123.abc.420', '3abc-0010.xy')
Output: ('xx-xxe-123', 'xxx.xbc.420', 'xxxx-0010.xy')

Example 2

Input: @list = ('1234567.a', 'a-1234-bc', 'a.b.c.d.e.f')
Output: ('xxxx567.a', 'x-xxx4-bc', 'x.x.x.x.e.f')

Mask Code in Raku

The idea is to iterate over the input string and use a regex substitution to replace alphanumeric characters with “x” four times.

I started this task trying to use only one counter ($count), but quickly found that there are a number of edge cases where it would not work properly. So we need to manage separately the number of successful matches ($count) and the place where to start the next search ($/.to, i.e. the atom next to the last successful match).

Note that I initially used the \w character class, but then changed it to <[\w] - [_]> to remove the “_” (underscore) from it.

constant MAX = 4;
my @tests = <ab-cde-123  123.abc.420  3abc-0010.xy  1234567.a
             a-1234-bc  a.b.c.d.e.f  12__34567.a>;
for @tests -> $test {
    my $count = 0;
    my $result = $test;
    my $pos = 0;
    while $count < MAX {
        $count++ if $result ~~ s:c($pos)/<[\w] - [_]>/x/;
        $pos = $/.to;
    }
    say "$test\t => $result";
}

This script displays the following output:

$ raku ./mask-code.raku
ab-cde-123       => xx-xxe-123
123.abc.420      => xxx.xbc.420
3abc-0010.xy     => xxxx-0010.xy
1234567.a        => xxxx567.a
a-1234-bc        => x-xxx4-bc
a.b.c.d.e.f      => x.x.x.x.e.f
12__34567.a      => xx__xx567.a

Mask Code in Perl

I used a different strategy in Perl: the input string is split into an array of characters in order to test each character individually.

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

my @tests = qw<ab-cde-123  123.abc.420  3abc-0010.xy  1234567.a
               a-1234-bc  a.b.c.d.e.f  12__34567.a>;
for my $test (@tests) {
    my $result = "";
    my $count = 0;
    for my $char (split //, $test) {
        if ($count < MAX and $char =~ /[A-Za-z0-9]/) {
            $char = 'x';
            $count++;
        }
        $result .= $char;
    }
    say "$test\t => $result";
}

Note that this program would not work the same way as the Raku program above for strings containing non-ASCII alphanumeric characters, but I don’t really care, since we have not been given any indication on how to manage non-ASCII Unicode characters. It would not be difficult to obtain the same behavior as the Raku program with something like this:

    if ($count < MAX and $char =~ /[A-Za-z0-9]/ and $char ne '_') {

This script displays the following output:

$ perl ./mask-code.pl
ab-cde-123       => xx-xxe-123
123.abc.420      => xxx.xbc.420
3abc-0010.xy     => xxxx-0010.xy
1234567.a        => xxxx567.a
a-1234-bc        => x-xxx4-bc
a.b.c.d.e.f      => x.x.x.x.e.f
12__34567.a      => xx__xx567.a

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

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.