This is my response to The Weekly Challenge #290.
@ints
.
$i
and $j
such that:
$i != $j
0 <= ($i, $j) < scalar @ints
$ints[$i] == 2 * $ints[$j]
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]
We do not actually need to consider the indices, just the values. So let us do just that.
The value approach may run into problems if we allow 0
(zero).
The given examples do not have zero (nor negative values for that matter) in them,
but I'll have a go at it anyway. Later on...
#! /usr/bin/env raku
unit sub MAIN (*@ints where all(@ints) ~~ Int && @ints.elems > 0); # [1]
my @double = @ints.map: * * 2; # [2]
say so ( any(@ints) == any(@double) ); # [3]
[1] A slurpy array of integers, with at least one value.
[2] Get a version of the input, whith all the values doubled.
The first *
is a reference to the current value, and the second
one is the multiplication operator.
[3]
Do any
of the input values have the double
version in the modified array? This is the third rule. Note the so
Booelan coercer to reduce the result to a single Boolean value.
See docs.raku.org/routine/any for more information about any
.
See docs.raku.org/routine/so for more information about so
.
Running it:
$ ./double-exist 6 2 3 3
True
0$ ./double-exist 3 1 4 13
False
$ ./double-exist 2 1 4 2
True
Looking good.
Zero and negative values are ok(ish):
$ ./double-exist 1 -1
False
$ ./double-exist 1 0 -1 -2
True
$ ./double-exist 1 0 0 -1
True
0$ ./double-exist 1 0 -1
True
The last one is wrong, as the zero is a single value and the two indices are equal. This breaks the first rule.
Let us fix that:
File: double-exist-zero
#! /usr/bin/env raku
unit sub MAIN (*@ints where all(@ints) ~~ Int && @ints.elems > 0);
if @ints.grep(* == 0).elems > 1 # [1]
{
say True; # [1a]
}
else
{
my @unique = @ints.unique.grep: * != 0; # [2]
my @double = @unique.map: * * 2;
say so ( any(@unique) == any(@double) );
}
[1] Do we have at least 2 zeros? Success if we do.
[2] ) The double any
construct is a double loop in disguise.
We can optimize this by getting rid of duplicates (with unique
) before
the comparison. This will speed up the program noticeably if the input array is
very big.
See docs.raku.org/routine/unique for more information about unique
.
We have indeed solved the problem:
$ ./double-exist-zero 1 0 -1
False
$ ./double-exist-zero 1 0 0 -1
True
$ ./double-exist-zero 1 0 0 0 -1
True
This program is not so compact, but it does give the correct result. That should count for something...
$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.
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
Why treat the payload explicitly? We can leave it as a part of the number, and
just check the result against mod 10
.
#! /usr/bin/env raku
unit sub MAIN ($str, :v($verbose)); # [1]
my @digits = $str.flip.comb.grep: * ~~ /<[0..9]>/; # [2]
say ": Digits (reverse): @digits[]" if $verbose;
my $total = 0; # [3]
@digits.push(0) unless @digits.elems %% 2; # [4]
for @digits -> $a, $b # [5]
{
my $sum = $b * 2; $sum = $sum.comb.sum if $sum > 9; # [6]
say ": $a = $a" if $verbose;
say ": $b * 2 = { $b * 2 }, total = $sum" if $verbose;
$total += $a; # [7]
$total += $sum; # [8]
}
say ! so $total mod 10; # [9]
[1] Just a string; no constraints. (Or; no strings attached.)
[2] Reverse the string (with flip
), get an array
with the individual characters (with comb
) and keep the digits only
(with grep
).
See docs.raku.org/routine/flip for more information about flip
.
[3] The total sum will end up here.
[4] We are going to iterate over two and two values at a time, so add a trailing zero (which will not impact the total) if we have an odd number of elements. This will prevent an execution error.
[5] Iterate over two and two elements at a time.
[6] The second one we double, and reduce to a single digit if larger than 9.
[7] Add the first digit.
[8] And the doubled second one.
[9] We modula operation will return zero on success. We coerce this to a Boolean
value (with so
), and negate it (with !
).
Running it:
$ ./luhns-algorithm 17893729974
True
$ ./luhns-algorithm "4137 8947 1175 5904"
True
$ ./luhns-algorithm "4137 8974 1175 5904"
False
Looking good.
With verbose mode:
$ ./luhns-algorithm -v 17893729974
: Digits (reverse): 4 7 9 9 2 7 3 9 8 7 1
: 4 = 4
: 7 * 2 = 14, total = 5
: 9 = 9
: 9 * 2 = 18, total = 9
: 2 = 2
: 7 * 2 = 14, total = 5
: 3 = 3
: 9 * 2 = 18, total = 9
: 8 = 8
: 7 * 2 = 14, total = 5
: 1 = 1
: 0 * 2 = 0, total = 0
True
$ ./luhns-algorithm -v "4137 8947 1175 5904"
: Digits (reverse): 4 0 9 5 5 7 1 1 7 4 9 8 7 3 1 4
: 4 = 4
: 0 * 2 = 0, total = 0
: 9 = 9
: 5 * 2 = 10, total = 1
: 5 = 5
: 7 * 2 = 14, total = 5
: 1 = 1
: 1 * 2 = 2, total = 2
: 7 = 7
: 4 * 2 = 8, total = 8
: 9 = 9
: 8 * 2 = 16, total = 7
: 7 = 7
: 3 * 2 = 6, total = 6
: 1 = 1
: 4 * 2 = 8, total = 8
True
$ ./luhns-algorithm -v "4137 8974 1175 5904"
: Digits (reverse): 4 0 9 5 5 7 1 1 4 7 9 8 7 3 1 4
: 4 = 4
: 0 * 2 = 0, total = 0
: 9 = 9
: 5 * 2 = 10, total = 1
: 5 = 5
: 7 * 2 = 14, total = 5
: 1 = 1
: 1 * 2 = 2, total = 2
: 4 = 4
: 7 * 2 = 14, total = 5
: 9 = 9
: 8 * 2 = 16, total = 7
: 7 = 7
: 3 * 2 = 6, total = 6
: 1 = 1
: 4 * 2 = 8, total = 8
False
Note the elegant syntax for iterating over more than one element at a
time (in [5]). I could have done this with gather
/take
, and that would
have removed the ugly trailing zero hack.
So, here it is, for comparison.
File: luhns-algorithm-gather
#! /usr/bin/env raku
unit sub MAIN ($str, :v($verbose));
my @digits = $str.flip.comb.grep: * ~~ /<[0..9]>/;
say ": Digits (reverse): @digits[]" if $verbose;
my $seq := gather
{
my $index = 0;
for @digits -> $sum is copy; # [1]
{
unless $index++ %% 2 { $sum *= 2; $sum = $sum.comb.sum if $sum > 9; }
take $sum;
}
}
my $total = $seq.sum;
say ! so $total mod 10;
[1] Iterating gives a read only value. We want to change it (locally), so we add
is copy
.
Running it gives the expected result:
$ ./luhns-algorithm-gather 17893729974
True
$ ./luhns-algorithm-gather "4137 8947 1175 5904"
True
$ ./luhns-algorithm-gather "4137 8974 1175 5904"
False
And that's it.