## BLOG: The Weekly Challenge #067

Saturday, Jul 4, 2020| Tags: Perl, Raku

This week both tasks had one thing in common i.e. pairing two or more list. In the past, I have taken the help from CPAN module Algorithm::Combinatorics for such tasks. However this time, I wanted to get my hand dirty and came up with the following:

``````# generate all possible combinations
my @combinations = ('');
foreach my \$members (@data) {
@combinations = map {
my  \$member = \$_;
map \$member.\$_, @\$members
} @combinations;
}
``````

I used the same piece of work in both tasks.

Let me share my solutions to the Perl Weekly Challenge - 067.

## TASK #1 › Number Combinations

#### Submitted by Mohammad S Anwar

You are given two integers `\$m` and `\$n`. Write a script print all possible combinations of `\$n` numbers from the list 1 2 3 … \$m.

Every combination should be sorted i.e. [2,3] is valid combination but [3,2] is not.

After getting all possible combinations, using the above code, it was time to filter the list as per the requirements. The following code does the job for me. Here I had to get help from CPAN module `List::Util` supplied `uniq()` to filer unique entries.

``````# filter combinations
my %filter;
foreach my \$member (@combinations) {
my @uniq = uniq split //, \$member;
next unless (@uniq == \$n);
\$filter{ sprintf("[%s]", join ', ', sort @uniq) } = 1;
}
``````

Please find below the complete `sub comb()` which handles the core of the task Number Combinations.

``````sub comb {
my (\$m, \$n) = @_;

die "ERROR: Missing 'm'.\n"
unless defined \$m;
die "ERROR: Missing 'n'.\n"
unless defined \$n;
die "ERROR: Invalid m=\$m (m>0).\n"
unless (\$m > 0);
die "ERROR: Invalid n=\$n (n>0).\n"
unless (\$n > 0);
die "ERROR: Invalid n=\$n (n<=m).\n"
unless (\$n <= \$m);

# prepare data set
my @data = ();
push @data, [1..\$m] foreach (1..\$n);

# generate all possible combinations
my @combinations = ('');
foreach my \$members (@data) {
@combinations = map {
my  \$member = \$_;
map \$member.\$_, @\$members
} @combinations;
}

# filter combinations
my %filter;
foreach my \$member (@combinations) {
my @uniq = uniq split //, \$member;
next unless (@uniq == \$n);
\$filter{ sprintf("[%s]", join ', ', sort @uniq) } = 1;
}

# return sorted result
return sprintf("[ %s ]", join ', ', (sort keys %filter));
}
``````

Getting the above Perl `sub comb()` translated into Raku was the most fun part.

As I am a big fan of `method-chaining` of Raku, I never miss any opportunities. For generating combinations in Raku, I don’t need external module, as Raku has everything built-in I need it.

Raku Rocks !!!.

If you noticed, I didn’t have to filter the result of combinations. Less work for me as compared to my Perl solution. Chaining with `map()` and `join()` made it look like kids job.

It turned out to be a one-liner in Raku without being difficult to understand.

``````sub number-combinations(Int \$m where { \$m > 0 },
Int \$n where { \$n > 0 }) {
\$n > \$m and say "ERROR: Invalid n=\$n (n <= m)" and exit;

return sprintf("[ %s ]",
(1..\$m)
.combinations(\$n)
.map({ '['~\$_.join(',')~']' })
.join(', '));
}
``````

Using the above `sub comb()`, the end result becomes one-liner.

``````use strict;
use warnings;
use List::Util qw(uniq);

my \$m = \$ARGV[0];
my \$n = \$ARGV[1];

print join "\n", comb(\$m, \$n), '';
``````

Even Raku is not behind either. I must admit, it looks more attractive.

``````use v6.d;

sub MAIN(Int :\$m where { \$m > 0 } = 5, Int :\$n where { \$n > 0 } = 2) {
number-combinations(\$m, \$n).say;
}
``````

Below is the standard unit test dealing with usual test case.

``````use Test::More;
use List::Util qw(uniq);

is( comb(5, 2),
'[ [1, 2], [1, 3], [1, 4], [1, 5], [2, 3], [2, 4], [2, 5], [3, 4], [3, 5], [4, 5] ]',
'testing m=5; n=2' );
is( comb(5, 3),
'[ [1, 2, 3], [1, 2, 4], [1, 2, 5], [1, 3, 4], [1, 3, 5], [1, 4, 5], [2, 3, 4], [2, 3, 5], [2, 4, 5], [3, 4, 5] ]',
'testing m=5; n=3' );
is( comb(5, 4),
'[ [1, 2, 3, 4], [1, 2, 3, 5], [1, 2, 4, 5], [1, 3, 4, 5], [2, 3, 4, 5] ]',
'testing m=5; n=4' );
is( comb(5, 5),
'[ [1, 2, 3, 4, 5] ]',
'testing m=5; n=5' );

done_testing;
``````

Similarly, Raku unit test followed the same path.

``````use Test;

is number-combinations(5, 2),
'[ [1, 2], [1, 3], [1, 4], [1, 5], [2, 3], [2, 4], [2, 5], [3, 4], [3, 5], [4, 5] ]',
'testing m=5, n=2';
is number-combinations(5, 3),
'[ [1, 2, 3], [1, 2, 4], [1, 2, 5], [1, 3, 4], [1, 3, 5], [1, 4, 5], [2, 3, 4], [2, 3, 5], [2, 4, 5], [3, 4, 5] ]',
'testing m=5, n=3';
is number-combinations(5, 4),
'[ [1, 2, 3, 4], [1, 2, 3, 5], [1, 2, 4, 5], [1, 3, 4, 5], [2, 3, 4, 5] ]',
'testing m=5, n=4';
is number-combinations(5, 5),
'[ [1, 2, 3, 4, 5] ]',
'testing m=5, n=5';

done-testing;
``````

## TASK #2 › Letter Phone

#### Submitted by Mohammad S Anwar

You are given a digit string `\$S`. Write a script to print all possible letter combinations that the given digit string could represent.

As mentioned above, I borrowed the code to find the combinations as before. Luckily, I didn’t have to do anything else to the list.

``````sub phone_letters {
my (\$digits) = @_;

die "ERROR: Missing digits.\n"
unless defined \$digits;
die "ERROR: Invalid digits [\$digits].\n"
unless (\$digits =~ /^[0-9\*\#]+\$/);

# letter phone
my %letter_of = (
'1' => '_,@',
'2' => 'abc',
'3' => 'def',
'4' => 'ghi',
'5' => 'jlk',
'6' => 'mno',
'7' => 'pqrs',
'8' => 'tuv',
'9' => 'wxyz',
'0' => '0',
'#' => '#',
'*' => chr(9251),
);

# prepare data set
my @data = ();
foreach my \$digit (split //,\$digits) {
push @data, [ split //, \$letter_of{\$digit} ];
}

# generate all possible combinations
my @combinations = ('');
foreach my \$members (@data) {
@combinations = map {
my  \$member = \$_;
map \$member.\$_, @\$members
} @combinations;
}

return @combinations;
}
``````

With the power of Raku, I used the `map()` and reduction meta-operator `[]`, to get the job done.

``````sub phone-letters(Str \$digits where { \$digits ~~ /^ <[0..9\*\#]>+ \$/ }) {

# letter phone
my %letter-of = (
'1' => '_,@',
'2' => 'abc',
'3' => 'def',
'4' => 'ghi',
'5' => 'jlk',
'6' => 'mno',
'7' => 'pqrs',
'8' => 'tuv',
'9' => 'wxyz',
'0' => '0',
'#' => '#',
'*' => chr(9251),
);

# prepare data set
my @data = \$digits.comb.map({ [ %letter-of{\$_}.comb ] });

# generate all possible combinations
return map {qq{'\$_'}}, ([X~] @data);
}
``````

To handle the unicode character, I had to rely on the following line to help printing the utf8 encoding character.

``````binmode STDOUT, 'encoding(UTF-8)';
``````

After that it just becomes one liner as below:

``````use strict;
use warnings;

binmode STDOUT, 'encoding(UTF-8)';
print join "\n", phone_letters(\$ARGV[0]), '';
``````

With Raku, no need to deal with unicode character separately.

Raku Rocks once again !!!*

``````use v6.d;

sub MAIN(Str :\$digits where { \$digits ~~ /^ <[0..9\*\#]>+ \$/ } = '35') {
say sprintf("[ %s ]", phone-letters(\$digits).join(", "));
}
``````

Finally unit test to make it complete solution.

``````use Test::More;
use Test::Deep;

is_deeply( [ phone_letters('35') ],
[ 'dj', 'dl', 'dk', 'ej', 'el', 'ek', 'fj', 'fl', 'fk' ],
'testing digits 35' );
is_deeply( [ phone_letters('10') ],
[ '_0', ',0', '@0' ],
'testing digits 10' );
is_deeply( [ phone_letters('2#') ],
[ 'a#', 'b#', 'c#' ],
'testing digits 2#' );

done_testing;
``````

And Raku unit test goes alone the line.

``````use Test;

is sprintf("[ %s ]", phone-letters('35').join(", ")),
"[ 'dj', 'dl', 'dk', 'ej', 'el', 'ek', 'fj', 'fl', 'fk' ]",
'testing "35"';
is sprintf("[ %s ]", phone-letters('10').join(", ")),
"[ '_0', ',0', '@0' ]",
'testing "10"';
is sprintf("[ %s ]", phone-letters('2#').join(", ")),
"[ 'a#', 'b#', 'c#' ]",
'testing "2#"';

done-testing;
``````

That’s it for this week. Speak to you soon.

## SO WHAT DO YOU THINK ?

If you have any suggestions or ideas then please do share with us.