with Raku

This is my response to The Weekly Challenge #290.

You are given an array of integers,

Write a script to find if there exist two indices

`@ints`

.
Write a script to find if there exist two indices

`$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...

You are given a string

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.

`$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

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.