Perl Weekly Challenge: Week 290
Challenge 1:
Double Exist
You are given an array of integers,
@ints
.Write a script to find if there exist two indices
$i
and$j
such that:
1) $i != $j
2) 0 <= ($i, $j) < scalar @ints
3) $ints[$i] == 2 * $ints[$j]
Example 1
Input: @ints = (6, 2, 3, 3)
Output: true
For $i = 0, $j = 2
$ints[$i] = 6 => 2 * 3 => 2 * $ints[$j]
Example 2
Input: @ints = (3, 1, 4, 13)
Output: false
Example 3
Input: @ints = (2, 1, 4, 2)
Output: true
For $i = 2, $j = 3
$ints[$i] = 4 => 2 * 2 => 2 * $ints[$j]
First we need to setup storage for the result. By default, it is False
.
my $result = False;
Then we get the indices of @ints
with .keys()
and all pairs of those indices with
the .combinations(2)
method.
for @ints.keys.combinations(2) -> $combo {
For convenience we define $i
and $j
as the two members of each pair.
my $i = @$combo[0];
my $j = @$combo[1];
Then we test if conditions 1 and 3 in the spec are valid. Condition 2 will always be as far as I can see so there is no reason to test that.
if $i != $j && @ints[$i] == 2 * @ints[$j] {
If the conditions are valid, we set $result
to True
and stop examing combinations.
$result = True;
last;
}
}
Finally, we print the result.
say $result;
I've been using Perl 5.38 since the latest Ubuntu LTS operating system came out in April but I haven't fully explored all the new
features yet. One that I recently heard about which I will be using a lot is support for true
and false
values and a boolean type for variables. (see the builtin man page for details.) To use it, you need to add a line like this at the top of the script:
use builtin qw/ true false /;
Because this is still an experimental feature for now, you will need to surpress warnings about it like this:
no warnings qw/ experimental::builtin /;
Now $result
is what the documentation calls a "distinguished boolean value" with the default value false
.
my $result = false;
We also need to provide a replacement for Raku's .combinations()
but I already had that from previous challenges.
for my $combo (combinations([keys @ints], 2)) {
my $i = $combo->[0];
my $j = $combo->[1];
if ($i != $j && $ints[$i] == 2 * $ints[$j]) {
$result = true;
last;
}
}
Unfortunately, the boolean support provided so far doesn't extend to output so we have to explicitly print the words "true" and "false" based on the value of $result
.
say $result ? 'true' : 'false';
Challenge 2:
Luhn's Algorithm
You are given a string
$str
containing digits (and possibly other characters which can be ignored). The last digit is the payload; consider it separately. Counting from the right, double the value of the first, third, etc. of the remaining digits.For each value now greater than 9, sum its digits.
The correct check digit is that which, added to the sum of all values, would bring the total mod 10 to zero.
Return true if and only if the payload is equal to the correct check digit.
It was originally posted on reddit.
Example 1
Input: "17893729974"
Output: true
Payload is 4.
Digits from the right:
7 * 2 = 14, sum = 5
9 = 9
9 * 2 = 18, sum = 9
2 = 2
7 * 2 = 14, sum = 5
3 = 3
9 * 2 = 18, sum = 9
8 = 8
7 * 2 = 14, sum = 5
1 = 1
Sum of all values = 56, so 4 must be added to bring the total mod 10 to zero. The payload is indeed 4.
Example 2
Input: "4137 8947 1175 5904"
Output: true
Example 3
Input: "4137 8974 1175 5904"
Output: false
The spec is fairly clear as to how to solve this though I did run into a problem due to misinterpretation.
First we split up $str
into its' constituent digits. My mistake was to remove all the non-digit characters; it turns out
they are needed to maintain position.
my @digits = $str.comb;
The last digit in @digits
is removed and stored separately as the checksum.
my $checksum = @digits.pop;
Storage is reserved for the sum of the digits.
my $sum;
We go through all the indices of @digits
one by one in reverse order.
for @digits.keys.reverse -> $k {
If the character at that index is not a digit, we skip to the next one.
if @digits[$k] !~~ '0' .. '9' {
next;
}
If the index is odd, the value of the digit is doubled else it is left as is.
my $val = $k % 2 ?? @digits[$k] * 2 !! @digits[$k];
If the value is now greater than 9, its digits are seperated with .comb()
and added together with .sum()
.
The value is added to $sum
.
$sum += $val > 9 ?? $val.comb.sum !! $val;
}
If the remainder of $sum
modulo 10 is equal to the checksum, we print True
else False
.
say 10 - $sum % 10 == $checksum;
This is the Perl version:
my @digits = split //, $str;
my $checksum = pop @digits;
my $sum;
for my $k (reverse keys @digits) {
if ($digits[$k] !~ /\d/) {
next;
}
my $val = $k % 2 ? $digits[$k] * 2 : $digits[$k];
I had to provide an implementation of sum()
as Perl doesn't have it.
$sum += $val > 9 ? sum(split //, $val) : $val;
}
Once again, we have to explicitly print "true" and "false" based on the value of the conditional.
say 10 - $sum % 10 == $checksum ? 'true' : 'false';