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

/;

Written on October 7, 2024