April 2024 Archives

Perl Weekly Challenge 266: X Matrix

These are some answers to the Week 266, Task 2, 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 April 28, 2024 at 23:59). This blog post provides some solutions to this challenge. Please don’t read on if you intend to complete the challenge on your own.

Task 2: X Matrix

You are given a square matrix, $matrix.

Write a script to find if the given matrix is X Matrix.

A square matrix is an X Matrix if all the elements on the main diagonal and antidiagonal are non-zero and everything else are zero.

Example 1

Input: $matrix = [ [1, 0, 0, 2],
                   [0, 3, 4, 0],
                   [0, 5, 6, 0],
                   [7, 0, 0, 1],
                 ]
Output: true

Example 2

Input: $matrix = [ [1, 2, 3],
                   [4, 5, 6],
                   [7, 8, 9],
                 ]
Output: false

Example 3

Input: $matrix = [ [1, 0, 2],
                   [0, 3, 0],
                   [4, 0, 5],
                 ]
Output: true

The matrix items on the main diagonal (from top left to bottom right) are those whose row index is equal to the column index, such as @matrix[0][0] or @matrix[1][1], i.e. items 1, 3, 6, and 1 in the first example above.

The matrix items on the anti-diagonal (from top right to bottom left) are those whose row index plus the column index is equal to the matrix size - 1, such as @matrix[0][3] or @matrix[1][2], i.e. items 2, 4, 5 and 7 in the first example above.

X Matrix in Raku

We iterate over the items of the matrix using two nested loops. If an item on a diagonal or an anti-diagonal (see above) is zero, then we return False; if an item not on a diagonal or an anti-diagonal is not zero, then we also return False. If we arrive at the end of the two loops, then we have an X matrix and can return True.

sub is-x-matrix (@m) {
    my $end = @m.end; # end = size - 1
    for 0..$end -> $i {
        for 0..$end -> $j {
            if $i == $j or $i + $j == $end { # diag or antidiag
                return False if @m[$i][$j] == 0;
            } else {    # not diag or antidiag
                return False if @m[$i][$j] != 0;
            }
        }
    }
    # If we got here, it is an X-matrix
    return True;
}

my @tests = 
    [ [1, 0, 0, 2],
      [0, 3, 4, 0],
      [0, 5, 6, 0],
      [7, 0, 0, 1],
    ],
    [ [1, 2, 3],
      [4, 5, 6],
      [7, 8, 9],
    ],
    [ [1, 0, 2],                                                          
      [0, 3, 0],
      [4, 0, 5],
    ];
for @tests -> @test {
    printf "[%-10s...] => ", "@test[0]";
    say is-x-matrix @test;
}

Note that we display only the first row of each test matrix for the sake of getting a better formatting. This program displays the following output:

$ raku ./x-matrix.raku
[1 0 0 2   ...] => True
[1 2 3     ...] => False
[1 0 2     ...] => True

X Matrix in Perl

This is a port to Perl of the above Raku program. Please refer to the previous section if you need further explanations.

use strict;
use warnings;
use feature 'say';

sub is_x_matrix {
    my $m = shift;
    my $end = scalar @{$m->[0]} - 1; # $end = size - 1
    for my $i (0..$end) {
        for my $j (0..$end) {
            if ($i == $j or $i + $j == $end) { # diag or antidiag
                return "false" if $m->[$i][$j] == 0;
            } else {    # not diag or antidiag
                return "false" if $m->[$i][$j] != 0;
            }
        }
    }
    # If we got here, it is an X-matrix
    return "true";
}

my @tests = (
    [ [1, 0, 0, 2],
      [0, 3, 4, 0],
      [0, 5, 6, 0],
      [7, 0, 0, 1],
    ],
    [ [1, 2, 3],
      [4, 5, 6],
      [7, 8, 9],
    ],
    [ [1, 0, 2],                                                          
      [0, 3, 0],
      [4, 0, 5],
    ]
    );
for my $test (@tests) {
    printf "[%-10s...] => ", "@{$test->[0]}";
    say is_x_matrix $test;
}

This program displays the following output:

$ perl ./x-matrix.pl
[1 0 0 2   ...] => true
[1 2 3     ...] => false
[1 0 2     ...] => true

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

Perl Weekly Challenge 266: Uncommon Words

These are some answers to the Week 266, Task 1, 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 April 28, 2024 at 23:59). This blog post provides some solutions to this challenge. Please don’t read on if you intend to complete the challenge on your own.

Task 1: Uncommon Words

You are given two sentences, $line1 and $line2.

Write a script to find all uncommon words in any order in the given two sentences. Return ('') if none found.

A word is uncommon if it appears exactly once in one of the sentences and doesn’t appear in other sentence.

Example 1

Input: $line1 = 'Mango is sweet'
       $line2 = 'Mango is sour'
Output: ('sweet', 'sour')

Example 2

Input: $line1 = 'Mango Mango'
       $line2 = 'Orange'
Output: ('Orange')

Example 3

Input: $line1 = 'Mango is Mango'
       $line2 = 'Orange is Orange'
Output: ('')

We're given two sentences, but the words of these sentences can be processed as one merged collection of words: we only need to find words that appear once in the overall collection.

Uncommon Words in Raku

We use the words method to split the sentences into individual words. Then, in the light of the comment above, we merge the two word lists into a single Bag, using the (+) or Baggy addition operator,infix%E2%8A%8E). Finally, we select words that appear only once in the resulting Bag.

sub uncommon ($in1, $in2) {
    my $out = $in1.words ⊎ $in2.words; # Baggy addition 
    return grep {$out{$_} == 1}, $out.keys;
}

my @tests = ('Mango is sweet', 'Mango is sour'),
            ('Mango Mango', 'Orange'),
            ('Mango is Mango', 'Orange is Orange');
for @tests -> @test {
    printf "%-18s - %-18s => ", @test[0], @test[1];
    say uncommon @test[0], @test[1];
}

This program displays the following output:

$ raku ./uncommon.raku
Mango is sweet     - Mango is sour      => (sour sweet)
Mango Mango        - Orange             => (Orange)
Mango is Mango     - Orange is Orange   => ()

Uncommon Words in Perl

This is a port to Perl of the above Raku program. We use a hash instead of a Bag to store the histogram of words.

use strict;
use warnings;
use feature 'say';

sub uncommon {
    my %histo;
    $histo{$_}++ for map { split /\s+/ } @_;
    my @result = grep {$histo{$_} == 1} keys %histo;
    return @result ? join " ", @result : "''";
}

my @tests = ( ['Mango is sweet', 'Mango is sour'],
              ['Mango Mango', 'Orange'],
              ['Mango is Mango', 'Orange is Orange'] ); 
for my $test (@tests) {
    printf "%-18s - %-18s => ", $test->[0], $test->[1];
    say uncommon $test->[0], $test->[1];
}

This program displays the following output:

$ perl ./uncommon.pl
Mango is sweet     - Mango is sour      => sour sweet
Mango Mango        - Orange             => Orange
Mango is Mango     - Orange is Orange   => ''

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

Perl Weekly Challenge 265: Completing Word

These are some answers to the Week 265, Task 2, 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 April 21, 2024 at 23:59). This blog post provides some solutions to this challenge. Please don’t read on if you intend to complete the challenge on your own.

Task 2: Completing Word

You are given a string, $str containing alphanumeric characters and array of strings (alphabetic characters only), @str.

Write a script to find the shortest completing word. If none found return empty string.

A completing word is a word that contains all the letters in the given string, ignoring space and number. If a letter appeared more than once in the given string then it must appear the same number or more in the word.

Example 1

Input: $str = 'aBc 11c'
       @str = ('accbbb', 'abc', 'abbc')
Output: 'accbbb'

The given string contains following, ignoring case and number:
a 1 times
b 1 times
c 2 times

The only string in the given array that satisfies the condition is 'accbbb'.

Example 2

Input: $str = 'Da2 abc'
       @str = ('abcm', 'baacd', 'abaadc')
Output: 'baacd'

The given string contains following, ignoring case and number:
a 2 times
b 1 times
c 1 times
d 1 times

The are 2 strings in the given array that satisfies the condition:
'baacd' and 'abaadc'.

Shortest of the two is 'baacd'

Example 3

Input: $str = 'JB 007'
       @str = ('jj', 'bb', 'bjb')
Output: 'bjb'

The given string contains following, ignoring case and number:
j 1 times
b 1 times

The only string in the given array that satisfies the condition is 'bjb'.

The task specification does not state it explicitly, but the examples show that we should ignore case when comparing letters.

Completing Word in Raku

In Raku, we'll use a Bag, which is a collection of distinct elements that each have an integer weight assigned to them signifying how many copies of that element are considered "in the bag", to store a histogram of the letter frequencies, both for the input test string and the words to which it should be compared. The good thing about it is that we obtain directly a histogram of the input letter list, and that can use the Subset of or equal to operator,infix%E2%8A%86) to check directly the completing condition.

sub complete-word  ($in-str, @in-words) {
    my $letters = $in-str.comb.map({ .lc}).grep( /<[a..z]>/).Bag;
    my @result;
    for @in-words -> $word {
        push @result, $word if $letters ⊆ $word.comb.map({ .lc }).Bag;
    }
    return min(@result, :by( { $_.chars } )); 
}

my @tests = ('aBc 11c', ('accbbb', 'abc', 'abbc')),
            ('Da2 abc', ('abcm', 'baacd', 'abaadc')),
            ('JB 007', ('jj', 'bb', 'bjb'));
for @tests -> @test {
    printf "%-8s - %-20s => ", @test[0], "@test[1]";
    say complete-word @test[0], @test[1];
}

This program displays the following output:

$ raku ./complete-wortd.raku
aBc 11c  - accbbb abc abbc      => accbbb
Da2 abc  - abcm baacd abaadc    => baacd
JB 007   - jj bb bjb            => bjb

Completing Word in Perl

This is a port to Perl of the above Raku program. We use a hash instead of a Bag to store the histogram of the input letters. The use of the subset operator is replaced by a simple loop to find out whether any letter of the input test string is missing (or in smaller number) in the input words.

use strict;
use warnings;
use feature 'say';

sub complete_word {
    my ($in_str, @in_words) = @_;
    my %letters;
    $letters{$_}++ for grep { $_ =~ /[a-z]/ } map { lc } split //, $in_str;
    my @result;
    WORD: for my $word (@in_words) {
        my %word_let;
        $word_let{$_}++ for map { lc } split //, $word;
        for my $k (keys %letters) {
            next WORD unless exists $word_let{$k};
            next WORD if $letters{$k} > $word_let{$k};
        }   
        push @result, $word;
    }
    return (sort {length $a <=> length $b} @result)[0];
}

my @tests = ( ['aBc 11c', ['accbbb', 'abc', 'abbc']],
              ['Da2 abc', ['abcm', 'baacd', 'abaadc']],
              ['JB 007', ['jj', 'bb', 'bjb']]  );
for my $test (@tests) {
    printf "%-8s - %-10s => ", $test->[0], "$test->[1][0] ...";
    say complete_word $test->[0], @{$test->[1]};
}

This program displays the following output:

$ perl ./complete-wortd.pl
aBc 11c  - accbbb ... => accbbb
Da2 abc  - abcm ...   => baacd
JB 007   - jj ...     => bjb

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

Perl Weekly Challenge 265: 33% Appearance

These are some answers to the Week 265, Task 1, 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 April 21, 2024 at 23:59). This blog post provides some solutions to this challenge. Please don’t read on if you intend to complete the challenge on your own.

Task 1: 33% Appearance

You are given an array of integers, @ints.

Write a script to find an integer in the given array that appeared 33% or more. If more than one found, return the smallest. If none found then return undef.

Example 1

Input: @ints = (1,2,3,3,3,3,4,2)
Output: 3

1 appeared 1 times.
2 appeared 2 times.
3 appeared 4 times.

3 appeared 50% (>33%) in the given array.

Example 2

Input: @ints = (1,1)
Output: 1

1 appeared 2 times.

1 appeared 100% (>33%) in the given array.

Example 3

Input: @ints = (1,2,3)
Output: 1

1 appeared 1 times.
2 appeared 1 times.
3 appeared 1 times.

Since all three appeared 33.3% (>33%) in the given array.
We pick the smallest of all.

33% Appearance in Raku

We coerce the input array into a Bag, which is a collection of distinct elements that each have an integer weight assigned to them, signifying how many copies of that element are considered "in the bag". The good thing about it is that we obtain directly a histogram of the values in the input array. Note that we return Nil rather than undef when no solution because this is more in line with what Raku does in such cases.

sub thirty-three-pct (@in) {
    my $count = @in.elems;
    return Nil if $count == 0;
    my $limit = $count * .33;
    my $histo = @in.Bag;
    my @eligibles = grep { $histo{$_} > $limit }, $histo.keys;
    return @eligibles ?? @eligibles.min !! Nil;
}

my @tests = <1 2 3 3 3 3 4 2>, <1 2>, <1 2 3>, 
            <1 2 1 2 1 2 1 2>, <1 2 3 4 1 2 3 4>;
for @tests -> @test {
    printf "%-18s => ", "@test[]";
    say thirty-three-pct @test;
}

This program displays the following output:

$ raku ./33-pct.raku
1 2 3 3 3 3 4 2    => 3
1 2                => 1
1 2 3              => 1
1 2 1 2 1 2 1 2    => 1
1 2 3 4 1 2 3 4    => Nil

33% Appearance in Perl

This is a port to Perl of the above Raku program. We use a hash instead of a Bag to store the histogram of the input values.

use strict;
use warnings;
use feature 'say';

sub thirty_three_pct {
    my $count = scalar @_;
    return "Undef" if $count == 0;
    my $limit = $count * .33;
    my %histo;
    $histo{$_}++ for @_;
    my @eligibles = sort {$a <=> $b} 
                    grep { $histo{$_} > $limit } keys %histo;
    return @eligibles ? $eligibles[0] : "Undef";
}

my @tests = ([<1 2 3 3 3 3 4 2>], [<1 2>], [<1 2 3>],
             [<1 2 1 2 1 2 1 2>], [<1 2 3 4 1 2 3 4>]);
for my $test (@tests) {
    printf "%-18s => ", "@$test";
    say thirty_three_pct @$test;
}

This program displays the following output:

$ perl ./33-pct.pl
1 2 3 3 3 3 4 2    => 3
1 2                => 1
1 2 3              => 1
1 2 1 2 1 2 1 2    => 1
1 2 3 4 1 2 3 4    => Undef

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

Perl Weekly Challenge 264: Target Array

These are some answers to the Week 264, Task 2, 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 April 14, 2024 at 23:59). This blog post provides some solutions to this challenge. Please don’t read on if you intend to complete the challenge on your own.

Task 2: Target Array

You are given two arrays of integers, @source and @indices. The @indices can only contains integers 0 <= i < size of @source.

Write a script to create target array by insert at index $indices[i] the value $source[i].

Example 1

Input: @source  = (0, 1, 2, 3, 4)
       @indices = (0, 1, 2, 2, 1)
Output: (0, 4, 1, 3, 2)

@source  @indices  @target
0        0         (0)
1        1         (0, 1)
2        2         (0, 1, 2)
3        2         (0, 1, 3, 2)
4        1         (0, 4, 1, 3, 2)

Example 2

Input: @source  = (1, 2, 3, 4, 0)
       @indices = (0, 1, 2, 3, 0)
Output: (0, 1, 2, 3, 4)

@source  @indices  @target
1        0         (1)
2        1         (1, 2)
3        2         (1, 2, 3)
4        3         (1, 2, 3, 4)
0        0         (0, 1, 2, 3, 4)

Example 3

Input: @source  = (1)
       @indices = (0)
Output: (1)

The way the target array is constructed (as detailed in the examples) is somewhat weird, so that we cannot use array slices as I was originally hoping. We have to build the target array iteratively, item by item.

Also note that we are not told what to do if the @source and @indices arrays don't have the same size, so we will assume they have the same size.

Target Array in Raku

In order to built the target array in accordance with the rules explained in the examples, the easiest is to use the splice routine, which can add, remove or replace elements of an array. In this specific case, we don't remove or replace items of the array (this is why the third parameter of the splice function call is set to 0), but just add one value at a time.

sub target-array (@source, @indices) {
    my @target;
    for 0..@source.end -> $i {
        splice @target, @indices[$i], 0,  @source[$i];
    }
    return @target;
}

my @tests = ( (0, 1, 2, 3, 4), (0, 1, 2, 2, 1) ),
            ( (1, 2, 3, 4, 0), (0, 1, 2, 3, 0) ),
            ( (1,), (0,) );
for @tests -> @test {
    printf "%-10s - %-10s => ", "@test[0]", "@test[1]";
    say target-array @test[0], @test[1];
}

This program displays the following output:

$ raku ./target-array.raku
0 1 2 3 4  - 0 1 2 2 1  => [0 4 1 3 2]
1 2 3 4 0  - 0 1 2 3 0  => [0 1 2 3 4]
1          - 0          => [1]

Target Array in Perl

This is a port to Perl of the above Raku program. For our practical purpose, Perl's splice function behaves essentially the same way as Raku's splice routine.

use strict;
use warnings;
use feature 'say';

sub target_array  {
    my @source = @{$_[0]};
    my @indices = @{$_[1]};
    my @target;
    for my $i (0..$#source) {
        splice @target, $indices[$i], 0,  $source[$i];
    }
    return @target;
}

my @tests = ( [ [0, 1, 2, 3, 4], [0, 1, 2, 2, 1] ],
              [ [1, 2, 3, 4, 0], [0, 1, 2, 3, 0] ],
              [ [1,], [0,] ] );
for my $test (@tests) {
    printf "%-10s - %-10s => ", "@{$test->[0]}", "@{$test->[1]}";
    say join " ", target_array $test->[0], $test->[1];
}

This program displays the following output:

$ perl ./target-array.pl
0 1 2 3 4  - 0 1 2 2 1  => 0 4 1 3 2
1 2 3 4 0  - 0 1 2 3 0  => 0 1 2 3 4
1          - 0          => 1

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

Perl Weekly Challenge 264: Greatest English Letter

These are some answers to the Week 264, Task 1, 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 April 14, 2024 at 23:59). This blog post provides some solutions to this challenge. Please don’t read on if you intend to complete the challenge on your own.

Task 1: Greatest English Letter

You are given a string, $str, made up of only alphabetic characters [a..zA..Z].

Write a script to return the greatest english letter in the given string.

A letter is greatest if it occurs as lower and upper case. Also letter ‘b’ is greater than ‘a’ if ‘b’ appears after ‘a’ in the English alphabet.

Example 1

Input: $str = 'PeRlwEeKLy'
Output: L

There are two letters E and L that appears as lower and upper.
The letter L appears after E, so the L is the greatest english letter.

Example 2

Input: $str = 'ChaLlenge'
Output: L

Example 3

Input: $str = 'The'
Output: ''

Greatest English Letter in Raku

I first thought about a regex that could, for example, detect any pair of lowercase / uppercase letter, or vice versa. This turned out to be quite inconvenient.

So I went for a completely different approach: make two lists: one with uppercase letters and one with lowercase letters, except that we store the string’s lower case letters converted as upper case letters. Then we simply look for letters that are common to the two lists (and finally sort them appropriately).

In Raku, we will store the lists in Sets, which are immutable collections of distinct elements in no particular order. One advantage of using a Set is that this removes any duplicate from each list. The second advantage is that we can use the set intersection operator to find letters that are in both lists.

One final note: we use the (Unicode) predefined character classes <:Lu> and <:Ll> to distinguish uppercase from lowercase characters.

sub greatest-eng-let ($in) {
    my $uc = (grep { / <:Lu> / }, $in.comb).Set;
    my $lc = (map { .uc }, grep {/ <:Ll> /}, $in.comb).Set;
    return  ($uc ∩ $lc).keys.sort.tail // "''"; 
}

my @tests = < PeRlwEeKLy ChaLlenge The >;
for @tests -> $test {
    printf "%-15s => ", $test;
    say greatest-eng-let $test;
}

This program displays the following output:

$ raku ./greatest-eng-let.raku
PeRlwEeKLy      => L
ChaLlenge       => L
The             => ''

Greatest English Letter in Perl

Our Perl implementation essentially follows the same approach as the Raku program above: make two lists: one with uppercase letters and one with lowercase letters, except that we store the string’s lower case letters converted as upper case letters. Then we simply look for letters that are common to the two lists (and finally sort them appropriately).

In Perl, our two lists will be stored in hashes. Hashes, as Raku’s Sets, remove duplicates. There is no set intersection operator, but it is quite easy to use a loop or a grep to find common items.

use strict;
use warnings;
use feature 'say';

sub greatest_eng_let {
    my @in = split //, shift;
    my %uc = map { $_ => 1 } grep { $_ le 'Z' } @in; 
    my @common =  grep { exists $uc{$_}}
                 map { uc } grep {$_ gt 'Z'} @in;
    return (sort @common)[-1] // "''"; 
}

my @tests = qw < PeRlwEeKLy ChaLlenge The >;
for my $test (@tests) {
    printf "%-15s => ", $test;
    say greatest_eng_let $test;
}

This program displays the following output:

$ perl ./greatest-eng-let.pl
PeRlwEeKLy      => L
ChaLlenge       => L
The             => ''

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

Perl Weekly Challenge 263: Merge Items

These are some answers to the Week 263, Task 2, 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 April 7, 2024 at 23:59). This blog post provides some solutions to this challenge. Please don’t read on if you intend to complete the challenge on your own.

Task 2: Merge Items

You are given two 2-D array of positive integers, $items1 and $items2 where element is pair of (item_id, item_quantity).

Write a script to return the merged items.

Example 1

Input: $items1 = [ [1,1], [2,1], [3,2] ]
       $items2 = [ [2,2], [1,3] ]
Output: [ [1,4], [2,3], [3,2] ]

Item id (1) appears 2 times: [1,1] and [1,3]. Merged item now (1,4)
Item id (2) appears 2 times: [2,1] and [2,2]. Merged item now (2,3)
Item id (3) appears 1 time: [3,2]

Example 2

Input: $items1 = [ [1,2], [2,3], [1,3], [3,2] ]
       $items2 = [ [3,1], [1,3] ]
Output: [ [1,8], [2,3], [3,3] ]

Example 3

Input: $items1 = [ [1,1], [2,2], [3,3] ]
       $items2 = [ [2,3], [2,4] ]
Output: [ [1,1], [2,9], [3,3] ]

Merge Items in Raku

We iterate over the items of the two arrays and store in a hash (%total) the quantities. At the end, we reorganize the hash into two 2-D arrays of positive integers (to retrieve the input data format).

sub merge-items (@in1, @in2) {
    my %total;
    for (|@in1, |@in2) -> @items {
        %total{@items[0]} += @items[1];
    }
    return map { ($_, %total{$_} ) }, %total.keys.sort;
}

my @tests = ( ((1,1), (2,1), (3,2)), ((2,2), (1,3)) ),
            ( ((1,2), (2,3), (1,3), (3,2)), ((3,1), (1,3)) ),
            ( ((1,1), (2,2), (3,3)), ((2,3), (2,4)) );
for @tests -> @test {
    printf "%-15s - %-10s => ", "@test[0]", "@test[1]";
    say merge-items @test[0], @test[1];
}

This program displays the following output:

$ raku ./merge-items.raku
1 1 2 1 3 2     - 2 2 1 3    => ((1 4) (2 3) (3 2))
1 2 2 3 1 3 3 2 - 3 1 1 3    => ((1 8) (2 3) (3 3))
1 1 2 2 3 3     - 2 3 2 4    => ((1 1) (2 9) (3 3))

Merge Items in Perl

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

sub merge_items {
    my %total;
    for my $in (@_) {
        for my $items (@$in) {
            $total{$items->[0]} += $items->[1];
        }
    }
    return map { "[ $_  $total{$_} ] " } sort keys %total;
}

my @tests = ( [ [[1,1], [2,1], [3,2]], [[2,2], [1,3]] ],
              [ [[1,2], [2,3], [1,3], [3,2]], [[3,1], [1,3]] ],
              [ [[1,1], [2,2], [3,3]], [[2,3], [2,4]] ] );
for my $test (@tests) {
    printf "%-3s %-3s => ", "[$test->[0][0][0]", "$test->[0][0][1]] ...";
    say merge_items  @$test[0], @$test[1];
}

Note that, when printing the input test data, the program displays only the first item (the first pair) of each test.

This program displays the following output:

$ perl ./merge-items.pl
[1  1] ... => [ 1  4 ] [ 2  3 ] [ 3  2 ]
[1  2] ... => [ 1  8 ] [ 2  3 ] [ 3  3 ]
[1  1] ... => [ 1  1 ] [ 2  9 ] [ 3  3 ]

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

Perl Weekly Challenge 263: Target Index

These are some answers to the Week 263, Task 1, 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 April 7, 2024 at 23:59). This blog post provides some solutions to this challenge. Please don’t read on if you intend to complete the challenge on your own.

Task 1: Target Index

You are given an array of integers, @ints and a target element $k.

Write a script to return the list of indices in the sorted array where the element is same as the given target element.

Example 1

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

Sorted array: (1, 2, 2, 3, 4, 5)
Target indices: (1, 2) as $ints[1] = 2 and $k[2] = 2

Example 2

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

No element in the given array matching the given target.

Example 3

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

Sorted array: (1, 2, 2, 3, 4, 5)
Target index: (4) as $ints[4] = 4

In theory, it is not necessary to sort the input array. We could simply count the number of items that are less than the target value and those which are equal to the target value. This is algorithmically simpler and probably faster (at least for large arrays). However, with the small input arrays of the examples, there is no need for performance optimization. So we will use the built-in sort functions of Perl and Raku.

Target Index in Raku

We simply use the sort method to sort the input array and the grep routine as a filter to select the subscripts in the sorted array for which the values are equal to the target.

sub find-index ($target, @in) {
    my @sorted = @in.sort;
    my @out = grep {@sorted[$_] == $target}, 0..@sorted.end;
    return @out;
}

my @tests = (2, (1, 5, 3, 2, 4, 2)), 
            (6, (1, 2, 4, 3, 5)), 
            (4, (5, 3, 2, 4, 2, 1));
for @tests -> @test {
    printf "%d - %-15s => ", @test[0], "@test[1]";
    say find-index @test[0], @test[1];
}

This program displays the following output:

$ raku ./find-index.raku
2 - 1 5 3 2 4 2     => [1 2]
6 - 1 2 4 3 5       => []
4 - 5 3 2 4 2 1     => [4]

Target Index in Perl

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

use strict;
use warnings;
use feature 'say';

sub find_index {
    my $target = shift;
    my @sorted = sort { $a <=> $b } @_;
    my @out = grep {$sorted[$_] == $target} 0..$#sorted;
    return "@out" || "()";
}

my @tests = ( [2, [1, 5, 3, 2, 4, 2]], 
              [6, [1, 2, 4, 3, 5]], 
              [4, [5, 3, 2, 4, 2, 1]] );
for my $test (@tests) {
    printf "%d - %-15s => ", $test->[0], "@{$test->[1]}";
    say find_index  @$test[0], @{$test->[1]};
}

This program displays the following output:

$ perl ./find-index.pl
2 - 1 5 3 2 4 2     => 1 2
6 - 1 2 4 3 5       => ()
4 - 5 3 2 4 2 1     => 4

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 April 14, 2024. 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.