Perl Weekly Challenge 290.
My solutions (task 1 and task 2 ) to the The Weekly Challenge - 290.
Task 1: Double Exist
Submitted by: Mohammad Sajid Anwar
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]
Straightforwardly I could compare each entry to each other, an O(N2)
task. Alternatively, I could order the array, an N log N task, and search for a
double never decreasing $i
nor $j
, an O(N) task.
This leads to a two liner:
Example 1:
perl -E '
@a=@b=sort{$a<=>$b}@ARGV;$o="false";$d=shift @b; for(@a){defined $d||last;
$o=true,last if $d==2*$_;next if $d>2*$_; $d=shift @b; redo}say "@ARGV -> $o"
' 6 2 3 3
Results:
6 2 3 3 -> true
Example 2:
perl -E '
@a=@b=sort{$a<=>$b}@ARGV;$o="false";$d=shift @b; for(@a){defined $d||last;
$o=true,last if $d==2*$_;next if $d>2*$_; $d=shift @b; redo}say "@ARGV -> $o"
' 3 1 4 13
Results:
3 1 4 13 -> false
Example 3:
perl -E '
@a=@b=sort{$a<=>$b}@ARGV;$o="false";$d=shift @b; for(@a){defined $d||last;
$o=true,last if $d==2*$_;next if $d>2*$_; $d=shift @b; redo}say "@ARGV -> $o"
' 2 1 4 2
Results:
2 1 4 2 -> true
The full code follows:
1 # Perl weekly challenge 290
2 # Task 1: Double Exist
3 #
4 # See https://wlmb.github.io/2024/10/07/PWC290/#task-1-double-exist
5 use v5.36;
6 die <<~"FIN" unless @ARGV;
7 Usage: $0 N1 N2...
8 to find if a double exist in the secuence N1 N2...
9 FIN
10 my @sorted = my @doubles = sort {$a<=>$b} @ARGV;
11 my $candidate = shift @doubles;
12 my $output = "false";
13 for(@sorted){
14 last unless defined $candidate;
15 $output = "true",last if $candidate == 2*$_;
16 next if $candidate > 2*$_;
17 $candidate=shift @doubles;
18 redo;
19 }
20 say "@ARGV -> $output"
Examples:
./ch-1.pl 6 2 3 3
./ch-1.pl 3 1 4 13
./ch-1.pl 2 1 4 2
Results:
6 2 3 3 -> true
3 1 4 13 -> false
2 1 4 2 -> true
Task 2: Luhn’s Algorithm
Submitted by: Andrezgz
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
Instead of calculating a payload and checking that it coincides with the given one, I can simply check if the digits sum to 0 mod 10. I can use an array to avoid doubling and adding digits. This yields a one and a half liner.
perl -MList::Util=sum0 -E '
@d=(0,2,4,6,8,1,3,5,7,9);for(@ARGV){my $i;say "$_ -> ",sum0(map {$i++%2?$d[$_]:$_}
grep{/\d/}reverse split "")%10?"false":"true";}
' 17893729974 "4137 8947 1175 5904" "4137 8974 1175 5904"
Results:
17893729974 -> true
4137 8947 1175 5904 -> true
4137 8974 1175 5904 -> false
The full code is the same, but more explicit:
1 # Perl weekly challenge 290
2 # Task 2: Luhn’s Algorithm
3 #
4 # See https://wlmb.github.io/2024/10/07/PWC290/#task-2-luhn’s-algorithm
5 use v5.36;
6 use List::Util qw(sum0);
7 die <<~"FIN" unless @ARGV;
8 Usage: $0 S1 S2...
9 to check the digits in the strings S1 S2 with Luhn's algorithm
10 FIN
11 my @doubled=(0,2,4,6,8,1,3,5,7,9); # double a digit and add its digits.
12 for(@ARGV){
13 my $counter=0;
14 my @letters=split "";
15 my @reversed_digits=reverse grep {/\d/} @letters;
16 my @mapped=map {$counter++%2?$doubled[$_]:$_} @reversed_digits; # double every second digit
17 my $sum=sum0(@mapped)%10;
18 my $result=$sum?"false":"true";
19 say "$_ -> $result";
20 }
Example:
./ch-2.pl 17893729974 "4137 8947 1175 5904" "4137 8974 1175 5904"
Results:
17893729974 -> true
4137 8947 1175 5904 -> true
4137 8974 1175 5904 -> false
/;