Perl Weekly Challenge 177: Damm Algorithm and Palindromic Prime Cyclops

These are some answers to the Week 177 of the Perl Weekly Challenge organized by Mohammad S. Anwar.

Spoiler Alert: This weekly challenge deadline is due in a few of days from now (on Aug. 14, 2022 at 23:59). This blog post offers some solutions to this challenge, please don’t read on if you intend to complete the challenge on your own.

Task 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

The algorithm is a check digit algorithm named after H. Michael Damm, who presented it in 2004.

The process is quite simple. We’ll use the quasi-group table provided in the afore-mentioned Wikipedia article:

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

Damm Algorithm in Raku

The process is simple. We start with a temporary value of 0. For each digit in the input number, we look up the table with the temporary variable and the digit, and set the temporary variable to the integer found in the table. At the end, the number is valid is the temporary variable is 0. For our test, we will use the two examples provided in the task specification, and we will test all numbers in the 5700..5800 range.

my @damm =  < 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 >;

sub is-valid ($n) {
    my $t = 0;
    $t = @damm[$t][$_] for $n.comb;
    return $t == 0;
}

for 5724, 5727 -> $test {
    say $test, is-valid($test) ?? " is valid." !! " is not valid.";
}
say "\nValid numbers between 5700 and 5800 are: ";
for 5700..5800 -> $i {
    print "$i " if is-valid $i;
}
say "";

This program displays the following output:

$ raku ./damm-algo.raku
5724 is valid.
5727 is not valid.

Valid numbers between 5700 and 5800 are:
5708 5719 5724 5735 5743 5756 5762 5770 5781 5797

Damm Algorithm in Perl

The algorithm for finding the check digit is the same as the one for testing whether a number is valid. So, rather than simply testing the validity directly as we did in Raku, we’ll write a find_check subroutine to find the check digit. Then, a number will be valid if its check digit is 0. Thus, we sort of get the two functions for the price of one. Besides that, the process is essentially the same as in Raku. Check the Raku section above is you need further explanations.

use strict;
use warnings;
use feature qw/say/;

my @damm =  (
[ < 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 > ] );

sub find_check {
    my $n = shift;
    my $t = 0;
    $t = $damm[$t][$_] for split //, $n;
    return $t;
}

sub is_valid {
    my $n = shift;
    return find_check($n) == 0;
}

for my $test (5724, 5727) {
    say $test, is_valid($test) ? " is valid." : " is not valid.";
}
say "\nValid numbers between 5700 and 5800 are: ";
for my $i (5700..5800) {
    print "$i " if is_valid $i;
}
say "";

This program displays the following output:

$ perl  ./damm-algo.pl
5724 is valid.
5727 is not valid.

Valid numbers between 5700 and 5800 are:
5708 5719 5724 5735 5743 5756 5762 5770 5781 5797

Damm Algorithm in Other Languages

We now present the same Damm algorithm in the following 18 guest languages (in alphabetic order):

  • awk
  • C
  • D
  • Dart
  • Go
  • Java
  • JavaScript
  • Julia
  • Kotlin
  • Lua
  • Nim
  • Pascal
  • Python
  • Ring
  • Ruby
  • Rust
  • Scala
  • Tcl

From now on, for all guest language implementations, the test will consist in listing the valid numbers between 5700 and 5800, a range that includes the two test cases (5724 and 5727) suggested in the task specification.

Damm Algorithm in awk

The awk programming language has minimal support for arrays. It doesn’t seem to support two-dimensional arrays, and iniitializing arrays in a pain in the neck (there may be some way, but the documentation is scarce). So we implement the damm lookup array as an array of strings and initialize it by initializing each item in the array (in the populate_damm function). Then we use the substr built-in function to retrieve the wanted value from the strings.

function is_valid(n) {
    t = 0
    for (j = 1; j <= length(n); j++) {
        t = substr(damm[t],substr(n, j, 1) + 1 ,1)
    }
    return t == 0
}
function populate_damm() {
    damm[0] = "0317598642"
    damm[1] = "7092154863"
    damm[2] = "4206871359"
    damm[3] = "1750983426"
    damm[4] = "6123045978"
    damm[5] = "3674209581"
    damm[6] = "5869720134"
    damm[7] = "8945362017"
    damm[8] = "9438617205"
    damm[9] = "2581436790"
}
BEGIN {
    populate_damm()
    print("Valid numbers between 5700 and 5800 are: ")
    for (i  = 5700; i<= 5800; i++) {
        if (is_valid(i)) {
            printf("%d ", i)
        }
    }
}

Output:

$ awk -f ./damm-algo.awk
Valid numbers between 5700 and 5800 are:
5708 5719 5724 5735 5743 5756 5762 5770 5781 5797

Note that I was thinking about a bc implementation of the Damm algorithm, but I gave up the idea because the situation with arrays (and also documentation) is worse that in awk.

Damm Algorithm in C

Not much to say, the code is quite clear. Just note that, in the temp = damm[temp][str[i] - '0']; code line, we need to subtract the Ascii value of 0 (48) from the value in the str integer-to-string conversion in order to get proper subscripts. We will have to do the same in a number of other guest language implementations.

#include <stdio.h>

const char damm[10][10] = {
        {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},
    };

int is_valid(int num) {
    int temp = 0;
    char str[10];
    int len = sprintf(str, "%d", num);   // convert input int to string
    str[len] = '\0';
    for (int i = 0; i < len; i++) {
         temp = damm[temp][str[i] - '0'];
    }
    return temp == 0;
}

int main() {
    printf("%s\n", "Valid numbers between 5700 and 5800 are: ");
    for (int i = 5700; i < 5800; i++) {
        if (is_valid(i)) 
            printf("%d ", i);
    }
    printf("%s\n", "");
}

Output:

$ ./a.out
Valid numbers between 5700 and 5800 are:
5708 5719 5724 5735 5743 5756 5762 5770 5781 5797

Damm Algorithm in D

As usual, the D syntax is very similar to the C syntax, but D is just a bit simpler to use than C.

import std.stdio;
import std.conv;

auto damm = [
    [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],
];

bool is_valid (int num) {
    string str = to!string(num, 10);
    int temp = 0;
    foreach (ch; str) {
        temp = damm[temp][ch - '0'];
    }
    return temp == 0;
}

void main() {
    writeln("Valid numbers between 5700 and 5800 are: ");
    for (int i = 5700; i < 5800; i++) {
        if (is_valid(i)) 
            printf("%d ", i);
    }
    writeln("");
}

Output:

Valid numbers between 5700 and 5800 are: 
5708 5719 5724 5735 5743 5756 5762 5770 5781 5797

Damm Algorithm in Dart

Like in awk, I haven’t found a good way to declare and initialize two-dimensional arrays in Dart, and the documentation is quite sparse. So I used the same solution as in awk: an array of strings.

import "dart:io";

var damm = [ "0317598642",
             "7092154863",
             "4206871359",
             "1750983426",
             "6123045978",
             "3674209581",
             "5869720134",
             "8945362017",
             "9438617205",
             "2581436790" ];

void main() {
    print("Valid numbers between 5700 and 5800 are:");
    for (int i = 5700; i <= 5800; i++ ) {
        if (is_valid(i)) {
            stdout.write("$i ");
        }
    }
    stdout.write("\n");
}
bool is_valid(n) {
    var digits = n.toString().split("");
    int temp = 0;
    var len = digits.length;
    for (int i = 0; i < len; i++) { 
        var row = damm[temp];
        var idx = int.parse(digits[i]);
        temp = int.parse(row[idx]);
    }
    return temp == 0;
}

Output:

Valid numbers between 5700 and 5800 are:
5708 5719 5724 5735 5743 5756 5762 5770 5781 5797

Damm Algorithm in Go

import (
    "fmt"
    "strconv"
)

var damm = [10][10] int {
    {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},
}

func is_valid(num int) bool {
    var n_str = strconv.Itoa(num)
    var temp = 0
    for _, ch := range n_str {
        temp = damm[temp][ch-'0']
    }
    return temp == 0
}

func main() {
    fmt.Println("Valid numbers between 5700 and 5800 are:")
    for i := 5700; i <= 5800; i++ {
        if is_valid(i) {
            fmt.Printf("%d ", i)
        }
    }
    fmt.Println("")
}

Output:

Valid numbers between 5700 and 5800 are:
5708 5719 5724 5735 5743 5756 5762 5770 5781 5797

Damm Algorithm in Java

public class DammCheckDigit {
    private static final int[][] damm = 
        { {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} };

    private static boolean is_valid(Integer num) {
        char[] n = num.toString().toCharArray();
        int temp = 0;
        for (char ch : n) {
            temp = damm[temp][ch - '0'];
        }
        return temp == 0;
    }

    public static void main(String[] args) {
        System.out.printf("%s", "Valid numbers between 5700 and 5800 are:");
        for(int i = 5700; i <= 5800; i++) {
            if (is_valid(i))  System.out.printf("%d ", i);
        }
        System.out.printf("%s", "\n");
    }
}

Output:

Valid numbers between 5700 and 5800 are:
5708 5719 5724 5735 5743 5756 5762 5770 5781 5797

Damm Algorithm in JavaScript

damm = [ [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] ];

function is_valid (n) {
    let digits = n.toString().split("")
    var temp = 0
    for (var i = 0; i < digits.length; i++) {
        temp = damm[temp][digits[i]]
    }
    return temp == 0
}

console.log("Valid numbers between 5700 and 5800 are:")
for (var i = 5700; i <= 5800; i++) {
    if (is_valid(i)) {
        process.stdout.write(i + " ")
    }
}
console.log("")

Output:

Valid numbers between 5700 and 5800 are:
5708 5719 5724 5735 5743 5756 5762 5770 5781 5797

Damm Algorithm in Julia

Julia array indices start at 1, not 0, so the crucial code line (temp = damm[temp + 1][d - '0' + 1]) needs to add 1 to the array subscripts when retrieving values from the damm matrix.

function is_valid(num)
    damm = (
        (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))
    temp = 0
    str = string(num)
    for d in str
        temp = damm[temp + 1][d - '0' + 1]
    end
    return temp == 0
end

println("Valid numbers between 5700 and 5800 are: ")
for i in 5700:5800
    if is_valid(i)
        print("$i ")
    end
end
println("")

Output:

$ julia ./damm-algo.jl
Valid numbers between 5700 and 5800 are:
5708 5719 5724 5735 5743 5756 5762 5770 5781 5797

Damm Algorithm in Kotlin

Declaring and initializing a matrix in Kotlin is somewhat painful, but the code is otherwise fairly straight forward.

val damm = arrayOf(
    intArrayOf(0, 3, 1, 7, 5, 9, 8, 6, 4, 2),
    intArrayOf(7, 0, 9, 2, 1, 5, 4, 8, 6, 3),
    intArrayOf(4, 2, 0, 6, 8, 7, 1, 3, 5, 9),
    intArrayOf(1, 7, 5, 0, 9, 8, 3, 4, 2, 6),
    intArrayOf(6, 1, 2, 3, 0, 4, 5, 9, 7, 8),
    intArrayOf(3, 6, 7, 4, 2, 0, 9, 5, 8, 1),
    intArrayOf(5, 8, 6, 9, 7, 2, 0, 1, 3, 4),
    intArrayOf(8, 9, 4, 5, 3, 6, 2, 0, 1, 7),
    intArrayOf(9, 4, 3, 8, 6, 1, 7, 2, 0, 5),
    intArrayOf(2, 5, 8, 1, 4, 3, 6, 7, 9, 0)
)

fun is_valid(num: Int): Boolean {
    val n_str = num.toString()
    var temp = 0
    for (d in n_str) {
        temp = damm[temp][d - '0']
    }
    return temp == 0
}

fun main() {
    println("Valid numbers between 5700 and 5800 are: ")
    for (i in 5700..5800) {
        if (is_valid(i)) print("$i ")
    }
    println("")
}

Output:

Valid numbers between 5700 and 5800 are: 
5708 5719 5724 5735 5743 5756 5762 5770 5781 5797

Damm Algorithm in Lua

Like in Julia, Lua array indices start at 1, not 0, so the crucial code line (temp = damm[temp + 1][ch + 1]) needs to add 1 to the array subscripts when retrieving values from the damm matrix.

local damm = {
    {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}
}
function is_valid(num)
    local n_str = tostring(num)
    local temp = 0
    for ch in n_str:gmatch"." do
        temp = damm[temp + 1][ch + 1]
    end
    return temp == 0
end

print("Valid numbers between 5700 and 5800 are: ")
for i = 5700, 5800 do
    if is_valid(i) then
        io.write(i, " ")
    end
end
print("")

Output:

Valid numbers between 5700 and 5800 are: 
5708 5719 5724 5735 5743 5756 5762 5770 5781 5797

Damm Algorithm in Nim

Remember that Nim control flow is indentation-based, like Python.

import strutils

let damm = [ [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] ]

proc is_valid(num: int): bool =
  let n_str = intToStr(num)
  var temp = 0
  for i in 0..len(n_str) - 1:
    temp = damm[temp][int(n_str[i]) - 48]
  return temp == 0

echo "Valid numbers between 5700 and 5800 are:"
for i in 5700..5800:
  if is_valid(i):
    stdout.write i, " "
echo ""

Output:

Valid numbers between 5700 and 5800 are:
5708 5719 5724 5735 5743 5756 5762 5770 5781 5797

Damm Algorithm in Pascal

I’m not particularly fond of Pascal, which I found too verbose (at least, by today’s criteria), and I’ve never used it for real-life projects (well, except some quite small Delphi projects a number of decades ago). But, having said that, I must add that Pascal is nonetheless a soft point for me, as it is the first language that I learned in my IS studies (although I had programmed as an autodidact before starting my studies, in Basic, in C, and in the pseudo-assembler of programmable pocket calculators). Pascal is quite clean (perhaps too clean) and it is with Pascal that I first learned the tenets of structured programming. Other languages that I used at the time and found interesting were Fortran, Ada, Prolog, Caml, and especially Modula-2 (or, was it Modula-3? Quite possibly both, one after the other. I’m not quite sure). I might try implementations in some of these languages some day, although I have forgotten most about them. Another language that I also (vaguely) learned at the time of my early studies is Cobol, but I disliked it so much that it is very unlikely that I will ever try to code something in it in the future.

program DammCheckDigit;
uses
  sysutils;

const damm : array[0..9,0..9] of integer =
    ( (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) );

function is_valid(num : integer) : boolean;
var
    temp, i : integer;
    n_str : string;
begin
    n_str := inttostr(num);
    temp := 0;
    for i := 0 to length(n_str) do
    begin
        temp := damm[temp, ord(n_str[i]) - ord ('0')];
    end;
    is_valid := temp=0;
end;

var
    i : integer;
begin
    writeln('Valid numbers between 5700 and 5800 are:');
    for i := 5700 to 5800 do
    begin
        if is_valid(i) then
             write(i, ' ');
    end;
    writeln
end.

{
Valid numbers between 5700 and 5800 are:
5708 5719 5724 5735 5743 5756 5762 5770 5781 5797 
}

Damm Algorithm in Python

damm = (
    (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)
)

def is_valid(num: int) -> bool:
  temp = 0
  for d in str(num):
    temp = damm[temp][int(d)] 
  return temp == 0

print("Valid numbers between 5700 and 5800 are:")
for i in range(5700, 5801):
  if is_valid(i):
    print(i, end=' ')
print("")

Output:

$ python3 ./damm-algo.py
Valid numbers between 5700 and 5800 are:
5708 5719 5724 5735 5743 5756 5762 5770 5781 5797

Damm Algorithm in Ring

Like in Julia and Lua, array indices start at 1, not 0, so the crucial code line (temp = damm[temp + 1][ch - '0' + 1]) needs to add 1 to the array subscripts when retrieving values from the damm matrix.

damm = [ [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] ]

see "Valid numbers between 5700 and 5800 are:" + nl
for i = 5700 to 5800
    if is_valid(i)
        see "" + i + " "
    ok
next
see " " + nl

func is_valid(num)
    temp = 0
    n = string(num)
    for ch in n
        temp = damm[temp + 1][ch - '0' + 1]
    next
    return temp = 0

Output:

$ ring ./damm-algo.ring
Valid numbers between 5700 and 5800 are:
5708 5719 5724 5735 5743 5756 5762 5770 5781 5797

Damm Algorithm in Ruby

The Ruby implementation is very simple and very concise, thanks to the built-in digits function. Note, however, that this function returns the digits in an inverse order, so we need to reverse it.

def is_valid (n)
    damm = [ [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] ]
    temp = 0
    for ch in n.digits.reverse
        temp = damm[temp][ch]
    end
    return temp == 0
end

puts("Valid numbers between 5700 and 5800 are:")
for i in 5700..5800
    if is_valid(i)
        printf("%d ", i)
    end
end
puts("")

Output:

Valid numbers between 5700 and 5800 are:
5708 5719 5724 5735 5743 5756 5762 5770 5781 5797

Damm Algorithm in Rust

fn is_valid(num: u32) -> bool {
    static DAMM: [[u8; 10]; 10] = [
        [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],
    ];

    let mut temp = 0;
    let n_str = num.to_string();
    let digits = n_str.bytes();
    for i in digits {
        temp = DAMM[temp as usize][i as usize - 48];
    };
    return temp == 0
}

fn main() {
    println!("{}", "Valid numbers between 5700 and 5800 are:");
    for i in 5700..5800 {
        if is_valid(i) {
            print!("{} ", i);
        }
    }
    println!("{}", " ");
}

Output:

Valid numbers between 5700 and 5800 are:
5708 5719 5724 5735 5743 5756 5762 5770 5781 5797

Damm Algorithm in Scala

object DammCheckDigit extends App {
  var damm =
    Vector(
      Vector(0, 3, 1, 7, 5, 9, 8, 6, 4, 2),
      Vector(7, 0, 9, 2, 1, 5, 4, 8, 6, 3),
      Vector(4, 2, 0, 6, 8, 7, 1, 3, 5, 9),
      Vector(1, 7, 5, 0, 9, 8, 3, 4, 2, 6),
      Vector(6, 1, 2, 3, 0, 4, 5, 9, 7, 8),
      Vector(3, 6, 7, 4, 2, 0, 9, 5, 8, 1),
      Vector(5, 8, 6, 9, 7, 2, 0, 1, 3, 4),
      Vector(8, 9, 4, 5, 3, 6, 2, 0, 1, 7),
      Vector(9, 4, 3, 8, 6, 1, 7, 2, 0, 5),
      Vector(2, 5, 8, 1, 4, 3, 6, 7, 9, 0)
    )

  def is_valid(num: Int): Boolean = {
    val num_str = num.toString.getBytes
    var temp = 0
    for (ch <- num_str) {
      temp = damm(temp)(ch - '0')
    }
    return temp == 0
  }

  println("Valid numbers between 5700 and 5800 are:")
  for (i <- 5700 to 5800) {
    if (is_valid(i)) {
      printf("%d ", i)
    }
  }
  println("")
}

Output:

Valid numbers between 5700 and 5800 are:
5708 5719 5724 5735 5743 5756 5762 5770 5781 5797

Damm Algorithm in Tcl

As in awk and Dart, I haven’t found a good way to declare and initialize two-dimensional arrays in Tcl, and the documentation is quite sparse. So I used the same solution as in awk and Dart for the conversion matrix: an array of strings. My knowledge of Tcl dates from the 1990s and is very rusty, so I initially had an interpreter error on almost every code line. So, be aware that my implementation certainly doesn’t satisfy the best practices of this language.

proc is_valid {n} {
    set damm(0) "0317598642"
    set damm(1) "7092154863"
    set damm(2) "4206871359"
    set damm(3) "1750983426"
    set damm(4) "6123045978"
    set damm(5) "3674209581"
    set damm(6) "5869720134"
    set damm(7) "8945362017"
    set damm(8) "9438617205"
    set damm(9) "2581436790"

    set temp 0
    foreach ch [split $n {}] {
        set row $damm($temp)
        set temp [ string index $row $ch ]
    }
    return [ expr $temp == 0 ? 1 : 0]
}

puts "Valid numbers between 5700 and 5800 are: "
for {set i 5700} {$i <= 5800} {incr i} {
    if [ is_valid $i ] {
        puts -nonewline  "${i} "
    }
}
puts ""

Output:

$ tclsh ./damm-algo.tcl
Valid numbers between 5700 and 5800 are:
5708 5719 5724 5735 5743 5756 5762 5770 5781 5797

Task 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

Palindromic Prime Cyclops in Raku

In order to reduce the pointless computations, we’ll only test number ranges with an odd number of digits (100..999, 10000..99999, 1000000..9999999). As it turns out, the process is quite fast (about 2.6 seconds), so that performance enhancement wasn’t really required. I find it nonetheless better to avoid useless computations.

sub is-cyclops ($n) {
    my $length = $n.chars;
    return False if $length %% 2;
    my $mid = ($length - 1) /2;
    return False if substr($n, $mid, 1) != 0;
    return False if $n.comb[0..$mid-1] ~~ /0/;
    return False if $n.comb[$mid+1..$length-1] ~~ /0/;
    return True;
}

my $count = 0;
for |(100..999), |(10000..99999), |(1000000..9999999) -> $i {
    next unless $i eq $i.flip;
    next unless $i.is-prime;
    if is-cyclops $i {
        print "$i ";
        $count++;
        last if $count == 20;
    }
}
say "";

This program displays the following output:

$ time raku ./cyclops.raku
101 16061 31013 35053 38083 73037 74047 91019 94049 1120211 1150511 1160611 1180811 1190911 1250521 1280821 1360631 1390931 1490941 1520251

real    0m2,573s
user    0m0,015s
sys     0m0,015s

Palindromic Prime Cyclops in Perl

This is a port to Perl of the Raku program above. Since Perl doesn’t have a built-in is_prime subroutine, we roll out our own.

use strict;
use warnings;
use feature qw/say/;

sub is_cyclops {
    my $n = shift;
    my $len = length $n;
    return 0 if $len % 2 == 0;
    my $mid = ($len - 1) /2;
    return 0 if substr($n, $mid, 1) != 0;
    return 0 if (split //, $n)[0..$mid-1] =~ /0/;
    return 0 if (split //, $n)[$mid+1..$len-1] =~ /0/;
    return 1;
}

sub is_prime {
   my $n = shift;
   return 1 if $n == 2;
   return 0 if $n % 2 == 0;
   return 0 if $n == 1;
   my $p = 3;
   my $sqrt = sqrt $n;
   while ($p <= $sqrt) {
       return 0 if $n % $p == 0;
       $p += 2;
   }
   return 1;
}

my $count = 0;
for my $i (100..999, 10000..99999, 1000000..9999999) {
    next unless $i eq reverse $i;
    next unless is_cyclops $i;
    if (is_prime $i) {
        print "$i ";
        $count++;
        last if $count == 20;
    }
}

This program displays the following output:

$ perl ./cyclops.pl
101 16061 31013 35053 38083 73037 74047 91019 94049 1120211 1150511 1160611 1180811 1190911 1250521 1280821 1360631 1390931 1490941 1520251

Palindromic Prime Cyclops in Other Languages

We now present the same palindromic prime cyclop implementations in the following 8 guest languages:

  • Julia
  • JavaScript
  • Kotlin
  • Lua
  • Python
  • Ring
  • Ruby
  • Coconut

Palindromic Prime Cyclops in Julia

I like the concision of Perl and Raku postfix conditionals such as:

return False if substr($n, $mid, 1) != 0;
return False if $n.comb[0..$mid-1] ~~ /0/;
return False if $n.comb[$mid+1..$length-1] ~~ /0/;
return True;

In most other languages, you would need three code lines for each case, for example in JavaScript:

if (s[mid] != '0') {
    return false
}
if (s.slice(0, mid-1).search('0') >= 0) {
    return false
}
if (s.slice(mid+1).search('0') >= 0) {
    return false
}
return true

Julia doesn’t have postfix conditionals, but you can use short-circuit evaluation of the && and || operators as an alternative to short if statements to reach the same concision.

The short-circuit evaluation, which is common to most imperative programming languages, means that:

  • In the expression a && b, the subexpression b is only evaluated if a evaluates to true.
  • In the expression a || b, the subexpression b is only evaluated if a evaluates to false.

Instead of if <cond> <statement> end, one can write <cond> && <statement> (which could be read as: <cond> and then <statement>). Similarly, instead of if ! <cond> <statement> end, one can write || (which could be read as: <cond> or else <statement>). This idiomatic alternative to short if statement is frequently used in Julia. Although most languages have the short-circuit behavior of logical operators, not many of them support this construct with an ending statement. Raku and Perl do, but this is not commonly used because the return false if ... is admittedly clearer. In the seven guest languages used here, only Kotlin also supports this construct.

So, in Julia, we will have, for example:

len % 2 == 0 && return false
...
s[mid] == '0' || return false

This is our Julia implementation using this concise construct:

using Primes

function is_cyclops(n)
    s = string(n)
    len = length(s)
    len % 2 == 0 && return false
    mid = Int((len + 1) /2)
    s[mid] == '0' || return false
    if occursin('0', s[1:mid-1]) || occursin('0', s[mid+1:len])
        return false
    end
    return true;
end

count = 0
for i = [101:999; 10000:99999; 1000000:9999999]
    string(i) != reverse(string(i)) && continue
    is_cyclops(i) || continue
    if isprime(i)
        print("$i ")
        global count += 1
        count >= 20 && break
    end
end
println("")

Output:

$ julia ./cyclop.jl
101 16061 31013 35053 38083 73037 74047 91019 94049 1120211 1150511 1160611 1180811 1190911 1250521 1280821 1360631 1390931 1490941 1520251

Palindromic Prime Cyclops in JavaScript

JavaScript doesn’t have a built-in function for prime numbers (just as almost all other guest languages to follow). So we roll out our own is_prime function (ported from the Perl implementation above).

It also doesn’t seem possible to combine ranges as we’ve done previously in other languages, so we use a if ... else if ... else if ... construct to simulate range concatenation.

function is_cyclops (n) {
    let s = n.toString()
    let len = s.length
    if (len % 2 == 0) {
        return false
    }

    let mid = (len - 1) / 2
    if (s[mid] != '0') {
        return false
    }

    if (s.slice(0, mid-1).search('0') >= 0) {
        return false
    }
    if (s.slice(mid+1).search('0') >= 0) {
        return false
    }
    return true
}

function is_prime(n) {
    if (n == 2) {
        return true
    }
    if (n < 2 || n % 2 == 0) {
        return false
    }
    var p = 3
    let sqrt_n = Math.sqrt(n)
    while (p <= sqrt_n) {
        if (n % p == 0) {
            return false
        }
        p += 2
    }
    return true
}

var count = 0
var i = 100
while (count < 20) {
    i++
    if (i == 999) {
        i = 10000
    } else if (i == 99999) {
        i = 1000000
    } else if (i >= 9999999) {
        break
    }
    if (i.toString() != i.toString().split("").reverse().join("")) {
        continue;
    }
    if (! is_cyclops(i)) {
        continue;
    }
    if (is_prime(i)) {
        process.stdout.write(i + " ")
        count++
    }
}
console.log("")

Output:

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

Palindromic Prime Cyclops in Kotlin

I do not know whether the short-circuit evaluation alternative to short if statement described above in the context of the Julia language is as commonly used in Kotlin as it is in Julia, but, since it works in Kotlin, I’ll use it here. Also note that we need to implement our own is_prime function in Kotlin.

fun is_prime(n: Int): Boolean {
    n == 2 && return true
    if (n < 2 || n % 2 == 0) {
        return false
    }
    var p = 3
    val sqrt_n : Int = Math.sqrt(n.toDouble()).toInt()
    while (p <= sqrt_n) {
        n % p == 0 && return false
        p += 2
    }
    return true
}

fun is_cyclops(num: Int): Boolean {
    val s = num.toString()
    val len = s.length
    len % 2 == 0 && return false
    val mid = ((len - 1) /2).toInt()
    s[mid] == '0' || return false
    if ('0' in s.slice(0 until mid) or '0' in s.slice(mid+1 until len) {
        return false
    }
    return true
}

fun main() {
    var count = 0
    var i = 100
    while (count < 20) {
        i++
        if (i == 999) {
             i = 10000
        } else if (i == 99999) {
            i = 1000000
        } else if (i >= 9999999) {
            break
        }
        if (i.toString() != i.toString().reversed()) {
            continue;
        }
        if (! is_cyclops(i)) {
            continue;
        }
        if (is_prime(i)) {
            print("$i ")
            count++
        }
    }
    println(" ")
}

Output:

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

Palindromic Prime Cyclops in Lua

Lua doesn’t have a next or continue statement to go directly to the next iteration of a loop, because “the language is designed to be small.” I’m not sure it is a very good reason, as, in my humble opinion, this kind of statement is very useful. This is the reason for which we have two goto skip statements in the main part of the program. I know very well what Edsger Dijkstra said in his famous letter to ACM “Go To Statement Considered Harmful,” but Donald Knuth commented that a “goto forward” within the same routine wasn’t that bad. This is the case here. Having said that, I would definitely prefer if Lua had a continue or next statement rather than a goto statement.

We also have to implement a is_prime function here.

local function is_cyclops(num)
    local n_str = tostring(num)
    size = string.len(n_str)
    if size % 2 == 0 then
        return false
    end
    mid = (size + 1) / 2
    if string.sub(n_str, mid, mid ) ~= '0' then
        return false
    end
    if string.sub(n_str, 1, mid-1):find "0" ~= nil then
        return false
    end
    if string.sub(n_str, mid+1, len):find "0" ~= nil then
        return false
    end
    return true
end

local function is_prime(n)
    if n == 2 then
        return true
    end
    if n < 2 or n % 2 == 0 then
        return false
    end
    local p = 3
    sqrt_n = math.sqrt(n)
    while p <= sqrt_n do
        if n % p == 0 then
            return false
        end
        p = p + 2
    end
    return true
end

count = 0
i = 100
while count < 20 do
    i = i + 1
    if i == 999 then
        i = 10000
    elseif i == 99999 then
        i = 1000000
    elseif i >= 9999999 then
        break
    end

    if i ~= tonumber(string.reverse(tostring(i))) then
        goto skip
    end
    if not is_cyclops(i) then
        goto skip
    end
    if is_prime(i) then
        io.write(i, " ")
        count = count + 1
    end
    ::skip::
end
print("")

Output:

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

Palindromic Prime Cyclops in Python

import math
from re import search
from itertools import chain

def is_prime(n):
  if n == 2:
    return True
  if n == 0 or n == 1 or n % 2 == 0:
    return False
  p = 3
  sqrt_n = math.sqrt(n)
  while (p <= sqrt_n):
    if ((n % p) == 0):
      return False
    p += 2
  return True

def is_cyclops (num):
  s = str(num)
  size = len(s)
  if size % 2 == 0:
    return False
  mid = int((size - 1) / 2)
  if s[mid] != '0':
    return False
  if search(r"0", s[0:mid-1]) or search(r"0", s[mid+1:size-1]):
    return False
  return True

count = 0
myrange = chain(range(100, 999), range(10000, 99999), range(1000000, 9999999))
for i in myrange:
  if str(i) != str(i)[::-1]:
    continue
  if not is_cyclops(i):
    continue
  if is_prime(i):
    print(i, end=' ')
    count += 1
    if count >= 20: 
      break

print("")

Output:

$ python3 ./cyclop.py
101 16061 31013 35053 38083 73037 74047 91019 94049 1120211 1150511 1160611 1180811 1190911 1250521 1280821 1360631 1390931 1490941 1520251

Palindromic Prime Cyclops in Ring

As far as I can tell, Ring also doesn’t have continue or next statements (and also no last and no break). Or, at least, I couldn’t find them (or an equivalent) in the documentation. So I had to use nested if statements, something that I don’t like too much. It also appears that there is no built-in function for reversing a string, so I wrote a is_palindrome function. And also a is_prime function. Also note that the equality operator (== in many languages) is spelled with a single equal sign in Ring, so we have for example if n % p = 0. I wonder how the compiler can distinguish it from the assignment operator, there must be some cases that are ambiguous.

i = 100
count = 0
while count < 20
    i++
    if i = 999
        i = 10000
    ok
    if i = 99999
        i = 1000000
    ok
    if is_palindrome(i)
        if is_cyclops(i)
            if is_prime(i)
                count++
                see "" + i + " "
            ok
       ok
   ok
end
see "" + nl

func is_prime (n)
    if n = 2 
        return true
    ok
    if n < 2 or n % 2 = 0
        return false
    ok
    p = 3
    sqrt_n = sqrt(n)
    while p < sqrt_n
        if n % p = 0
            return false
        ok
        p++
    end
    return true

func is_cyclops(n)
    s = "" + n
    size = len(s)
    if size % 2 = 0
        return false
    ok
    mid = ( size + 1) / 2
    if s[mid] != 0
        return false
    ok

    if substr(left(s, mid-1), "0") > 0
        return false
    ok
    if substr(right(s, mid-1), "0") > 0
        return false
    ok
    return true

func is_palindrome(n)
    s = "" + n
    size = len(s)
    for i = 1 to size/2
        if s[i] != s[size - i + 1]
            return false
        ok
    next
    return true

Output:

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

Palindromic Prime Cyclops in Ruby

Ruby has a built-in prime function, so we don’t need to implement it. I found a way to combine ranges, but I must admit that this is copied from a post on Stack Overflow, I would not have found it by myself.

require 'prime'

def is_cyclops (num) 
    s = num.to_s
    len = s.length;
    if len % 2 == 0 then
        return false
    end
    mid = (len - 1) / 2
    if s[mid] != '0' then
        return false
    end
    if s[0..mid-1][/0/] || s[mid+1..len-1][/0/]
        return false
    end 
    return true
end

count = 0;
range = [ 100..999, 10000..99999, 1000000..9999999 ].map { |r| Array(r) }.inject( :+ )
for i in range
    if i.to_s != i.to_s.reverse || ! is_cyclops(i)
        next
    end
    if i.prime?
        printf("%d ", i)
        count += 1;
        if count == 20
            break
        end
    end
end
puts ""

Output:

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

Palindromic Prime Cyclops in Coconut (a Functional Extension of Python)

This section on Coconut was added on August 18, 2022.

Coconut is a variant of Python built for functional programming. It is actually a kind of extension of Python, and Coconut code is in fact compiled to Python code. The documentation is still somewhat limited, but there is a good Coconut Tutorial and a reference Coconut Documentation.

I was interested in trying to use Coconut for this task essentially because of its support to pipeline style programming. Since we’re looking for the first twenty integers having a certain set of properties (prime, palindromic, with an odd number of digits, with a 0 in the middle and no other 0, etc.), it is appealing to use a pipeline of filters, one for each of these properties, as done in the last (main) section of the code below.

import math
from re import search

def is_prime(n):
    case n:
        match 0:
            return False
        match 1:
            return False
        match 2:
            return True
        match _ is int if n > 2:
            if n % 2 == 0:
                return False
            p = 3
            sqrt_n = math.sqrt(n)
            while (p <= sqrt_n):
                if (n % p) == 0:
                    return False
                p += 2
            return True

def is_cyclops (num):
  s = str(num)
  size = len(s)
  mid = int((size - 1) / 2)
  if s[mid] != '0':
    return False
  if search(r"0", s[0:mid-1]) or search(r"0", s[mid+1:size-1]):
    return False
  return True

range(100, 999) :: range(10000, 99999) :: range(1000000, 9000000) \
|> filter$( -> str(_) == str(_)[::-1]) \    # palindrome
|> filter$( -> len(str(_)) %2 != 0) \       # odd number of digits
|> filter$(is_cyclops) |> filter$(is_prime) \
|>  list |> .$[:20] |> print

Output:

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

Wrapping up

The next week Perl Weekly Challenge will start soon. If you want to participate in this challenge, please check https://perlweeklychallenge.org/ and make sure you answer the challenge before 23:59 BST (British summer time) on August 21, 2022. And, please, also spread the word about the Perl Weekly Challenge if you can.

Leave a comment

About laurent_r

user-pic I am the author of the "Think Perl 6" book (O'Reilly, 2017) and I blog about the Perl 5 and Raku programming languages.