Cyclops Be Dammed
with Raku

by Arne Sommer

Cyclops Be Dammed with Raku

[196] Published 13. August 2022.

This is my response to The Weekly Challenge #177.

Challenge #177.1: Damm Algorithm

You are given a positive number, $n.

Write a script to validate the given number against the included check digit.

Please checkout the wikipedia page for information.

Example 1:
Input: $n = 5724
Output: 1 as it is valid number
Example 2:
Input: $n = 5727
Output: 0 as it is invalid number

File: damm
#! /usr/bin/env raku

unit sub MAIN (Int $value where $value > 1, :v(:$verbose));  # [1]

my @ot =
(
  (0,3,1,7,5,9,8,6,4,2),                                     # [2]
  (7,0,9,2,1,5,4,8,6,3),
  (4,2,0,6,8,7,1,3,5,9),
  (1,7,5,0,9,8,3,4,2,6),
  (6,1,2,3,0,4,5,9,7,8),
  (3,6,7,4,2,0,9,5,8,1),
  (5,8,6,9,7,2,0,1,3,4),
  (8,9,4,5,3,6,2,0,1,7),
  (9,4,3,8,6,1,7,2,0,5),
  (2,5,8,1,4,3,6,7,9,0),
);

my $interim-digit = 0;                                      # [3]

for $value.comb -> $digit                                   # [4]
{
  print ": D:$digit I:$interim-digit" if $verbose;
  $interim-digit = @ot[$interim-digit][$digit];             # [5]
  say " -> $interim-digit" if $verbose;
}

say $interim-digit == 0 ?? "valid" !! "invalid";            # [6]

[1] A positive integer, not number as incorrectly stated in the challenge.

[2] We need the values from the matrix in the wikipedia article, so here they are. As a matrix, as that is indeed a handy format.

[3] The initial interim digit value is zero.

[4] Iterate over each digit in the number (i.e. integer).

[5] Get the new interim digit.

[6] The final interim digit (after we have run out of digits in [4]) has to be zero for the number to be valid. I have chosen to print the result as a text.

Running it:

$ ./damm 5724
valid

$ ./damm 5727
invalid

Verbose mode gives us the intermediary values as shown in the wikipedia article, albeit oriented in a different fashion:

$ ./damm -v 5724
: D:5 I:0 -> 9
: D:7 I:9 -> 7
: D:2 I:7 -> 4
: D:4 I:4 -> 0
valid

$ ./damm -v 5727
: D:5 I:0 -> 9
: D:7 I:9 -> 7
: D:2 I:7 -> 4
: D:7 I:4 -> 9
invalid

Ok. Let us try a Damm Sequence:

File: damm-seq
#! /usr/bin/env raku

unit sub MAIN (Int $count where $count > 0 = 20);

my $ds := (10 .. Inf).grep: *.&is-damm;

say $ds[^$count].join(", ");

sub is-damm (Int $value)       # [1]
{
  my @ot =
  (
    (0,3,1,7,5,9,8,6,4,2),
    (7,0,9,2,1,5,4,8,6,3),
    (4,2,0,6,8,7,1,3,5,9),
    (1,7,5,0,9,8,3,4,2,6),
    (6,1,2,3,0,4,5,9,7,8),
    (3,6,7,4,2,0,9,5,8,1),
    (5,8,6,9,7,2,0,1,3,4),
    (8,9,4,5,3,6,2,0,1,7),
    (9,4,3,8,6,1,7,2,0,5),
    (2,5,8,1,4,3,6,7,9,0),
  );

  my $interim-digit = 0;

  for $value.comb -> $digit
  {
    $interim-digit = @ot[$interim-digit][$digit];
  }

  return $interim-digit == 0;
}

[1] Nothing much to see here. I have wrapped the Damm detection code in a procedure, so this looks like the sequences shown in several of my responses to recent challenges.

Running it:

$ ./damm-seq
13, 21, 37, 45, 59, 68, 76, 84, 92, 101, 117, 125, 130, 149, 158, 163, 174, \
  182, 196, 207

$ ./damm-seq 200
13, 21, 37, 45, 59, 68, 76, 84, 92, 101, 117, 125, 130, 149, 158, 163, 174, \
  182, 196, 207, 210, 229, 232, 241, 255, 264, 278, 286, 293, 308, 319, 324, \
  335, 343, 356, 362, 370, 381, 397, 403, 416, 427, 434, 442, 450, 469, 475, \
  488, 491, 502, 515, 528, 531, 544, 553, 566, 577, 589, 590, 609, 614, 623, \
  638, 646, 651, 667, 672, 680, 695, 705, 718, 726, 739, 747, 752, 760, 771, \
  783, 794, 806, 811, 822, 833, 840, 854, 865, 879, 887, 898, 904, 912, 920, \
  936, 948, 957, 961, 973, 985, 999, 1007, 1010, 1029, 1032, 1041, 1055, \
  1064, 1078, 1086, 1093, 1108, 1119, 1124, 1135, 1143, 1156, 1162, 1170, \
  1181, 1197, 1203, 1216, 1227, 1234, 1242, 1250, 1269, 1275, 1288, 1291, \
  1300, 1313, 1321, 1337, 1345, 1359, 1368, 1376, 1384, 1392, 1402, 1415, \
  1428, 1431, 1444, 1453, 1466, 1477, 1489, 1490, 1509, 1514, 1523, 1538, \
  1546, 1551, 1567, 1572, 1580, 1595, 1601, 1617, 1625, 1630, 1649, 1658, \
  1663, 1674, 1682, 1696, 1706, 1711, 1722, 1733, 1740, 1754, 1765, 1779, \
  1787, 1798, 1804, 1812, 1820, 1836, 1848, 1857, 1861, 1873, 1885, 1899, \
  1905, 1918, 1926, 1939, 1947, 1952, 1960, 1971, 1983, 1994, 2008

The last one shows that we get approximately one tenth of the numbers. (The approximation comes from the fact that we started at 10 (instead of 1), and may have stoppet just before - or after - a Damm number.) This is logical, as we get exactly one control digit (out of ten possibilities).

We have actually generated all (up to a point that is) the positive integers, postfixed with the Damm check digit. It would have been faster to implement it this way, as we then would only generate the Damm check digit for the values we show instead of verifying them for the 9 out of 10 that do not verify.

File: damm-seq-faster
#! /usr/bin/env raku

unit sub MAIN (Int $count where $count > 0 = 20);

my $ds := (1 .. Inf).map({ $_ ~ is-damm(:get-check-digit, $_) });  # [1]

say $ds[^$count].join(", ");

sub is-damm (Int $value, :$get-check-digit)                        # [2]
{
  my @ot =
  (
    (0,3,1,7,5,9,8,6,4,2),
    (7,0,9,2,1,5,4,8,6,3),
    (4,2,0,6,8,7,1,3,5,9),
    (1,7,5,0,9,8,3,4,2,6),
    (6,1,2,3,0,4,5,9,7,8),
    (3,6,7,4,2,0,9,5,8,1),
    (5,8,6,9,7,2,0,1,3,4),
    (8,9,4,5,3,6,2,0,1,7),
    (9,4,3,8,6,1,7,2,0,5),
    (2,5,8,1,4,3,6,7,9,0),
  );

  my $interim-digit = 0;

  for $value.comb -> $digit
  {
    $interim-digit = @ot[$interim-digit][$digit];
  }

  return $get-check-digit                                           # [3]
    ?? $interim-digit
    !! $interim-digit == 0;
}

[1] map instead of grep this time.

[2] Note the named argument «get-check-digit» used to (surprise) get the check digit, instead of verifying the number. The old behaviour is still available, simply do not use the named argument.

See docs.raku.org/language/variables#The_:_twigil for more information about named parameters.

[3] New or old behaviour? Behave accordingly.

What about a Damm Prime Sequence, you may ask?

Note that this is based on «damm-seq» (and not «damm-seq-faster»). The reason is that I presume that «is-prime» is faster than «is-damm». I may well be wrong...

File: damm-prime-seq
#! /usr/bin/env raku

unit sub MAIN (Int $count where $count > 0 = 20);

my $ds := (10 .. Inf).grep({ $_.is-prime && $_.&is-damm });  # [1]

say $ds[^$count].join(", ");

sub is-damm (Int $value, :$get-check-digit)                                                             # [2]
{
  my @ot =
  (
    (0,3,1,7,5,9,8,6,4,2),
    (7,0,9,2,1,5,4,8,6,3),
    (4,2,0,6,8,7,1,3,5,9),
    (1,7,5,0,9,8,3,4,2,6),
    (6,1,2,3,0,4,5,9,7,8),
    (3,6,7,4,2,0,9,5,8,1),
    (5,8,6,9,7,2,0,1,3,4),
    (8,9,4,5,3,6,2,0,1,7),
    (9,4,3,8,6,1,7,2,0,5),
    (2,5,8,1,4,3,6,7,9,0),
  );

  my $interim-digit = 0;

  for $value.comb -> $digit
  {
    $interim-digit = @ot[$interim-digit][$digit];
  }

  return $get-check-digit
    ?? $interim-digit
    !! $interim-digit == 0; 
}

[1] I had to use the ({ ... }) syntax, as I refer to the current value twice (now as $_, instead of * if used only once, and without the braces).

[2] The same procedure as before, but used to verify (and not generate) the check digit.

Running it:

$ ./damm-prime-seq
13, 37, 59, 101, 149, 163, 229, 241, 293, 397, 491, 577, 739, 811, 887, \
  1093, 1181, 1291, 1321, 1453

Challenge #177.2: Palindromic Prime Cyclops

Write a script to generate first 20 Palindromic Prime Cyclops Numbers.

A cyclops number is a number with an odd number of digits that has a zero in the center only.

Output:
101, 16061, 31013, 35053, 38083, 73037, 74047, 91019, 94049,
1120211, 1150511, 1160611, 1180811, 1190911, 1250521, 1280821,
1360631, 1390931, 1490941, 1520251

Premature optimisation be damned, here we go again...

(That was a reference to last week.)

File: ppc
#! /usr/bin/env raku

unit sub MAIN (Int $count where $count > 0 = 20);

my $ppc := (1..Inf).map({ next if /0/; $_ ~ "0" ~ $_.flip }).grep: *.is-prime;
 # 1 #############  # 2 ############# # 3 ################## # 4 ############ 
say $ppc[^$count].join(", ");

[1] This should be familiar by now; a lazy sequence of integers from 1 to infinity.

[2] map is a loop in disguise, so using next inside it works (even though using grep is a more obvious choice. Normally. Here we use the next part to get rid of numbers containing at least one instance of the digit zero.

See docs.raku.org/syntax/next for more information about next.

[3] This part maps (yes, that is why the method is called map) the value into a new one - the original value, followed by a zero and the opriginal value reversed.

[4] We want primes only. Note the usage of : instead of parens. This works as this is the last part of the chained command. Also note the usage of * to refer to the current value. This works when we skip the curly braces. Use them, and go for $_ instead, if you want to.

Running it:

$ ./ppc
101, 16061, 31013, 35053, 38083, 73037, 74047, 91019, 94049, 1120211, 1150511, 1160611, 1180811, 1190911, 1250521, \
  1280821, 1360631, 1390931, 1490941, 1520251

Spot on, except the line breaks. But that is surely just a presentation detail...

And that's it.