Verhoeff algorithm

From Rosetta Code
Revision as of 11:37, 26 August 2021 by Wherrera (talk | contribs) (julia example)
Verhoeff algorithm is a draft programming task. It is not yet considered ready to be promoted as a complete task, for reasons that should be found in its talk page.
Description

The Verhoeff algorithm is a checksum formula for error detection developed by the Dutch mathematician Jacobus Verhoeff and first published in 1969. It was the first decimal check digit algorithm which detects all single-digit errors, and all transposition errors involving two adjacent digits, which was at the time thought impossible with such a code.

As the workings of the algorithm are clearly described in the linked Wikipedia article they will not be repeated here.

Task

Write routines, methods, procedures etc. in your language to generate a Verhoeff checksum digit for non-negative integers of any length and to validate the result. A combined routine is also acceptable.

The more mathematically minded may prefer to generate the 3 tables required from the description provided rather than to hard-code them.

Write your routines in such a way that they can optionally display digit by digit calculations as in the Wikipedia example.

Use your routines to calculate check digits for the integers: 236, 12345 and 123456789012 and then validate them. Also attempt to validate the same integers if the check digits in all cases were 9 rather than what they actually are.

Display digit by digit calculations for the first two integers but not for the third.

Related task



Go

Translation of: Wren

<lang go>package main

import "fmt"

var d = [][]int{

   {0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
   {1, 2, 3, 4, 0, 6, 7, 8, 9, 5},
   {2, 3, 4, 0, 1, 7, 8, 9, 5, 6},
   {3, 4, 0, 1, 2, 8, 9, 5, 6, 7},
   {4, 0, 1, 2, 3, 9, 5, 6, 7, 8},
   {5, 9, 8, 7, 6, 0, 4, 3, 2, 1},
   {6, 5, 9, 8, 7, 1, 0, 4, 3, 2},
   {7, 6, 5, 9, 8, 2, 1, 0, 4, 3},
   {8, 7, 6, 5, 9, 3, 2, 1, 0, 4},
   {9, 8, 7, 6, 5, 4, 3, 2, 1, 0},

}

var inv = []int{0, 4, 3, 2, 1, 5, 6, 7, 8, 9}

var p = [][]int{

   {0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
   {1, 5, 7, 6, 2, 8, 3, 0, 9, 4},
   {5, 8, 0, 3, 7, 9, 6, 1, 4, 2},
   {8, 9, 1, 6, 0, 4, 3, 5, 2, 7},
   {9, 4, 5, 3, 1, 2, 6, 8, 7, 0},
   {4, 2, 8, 6, 5, 7, 3, 9, 0, 1},
   {2, 7, 9, 3, 8, 0, 6, 4, 1, 5},
   {7, 0, 4, 6, 9, 1, 3, 2, 5, 8},

}

func verhoeff(s string, validate, table bool) interface{} {

   if table {
       t := "Check digit"
       if validate {
           t = "Validation"
       }
       fmt.Printf("%s calculations for '%s':\n\n", t, s)
       fmt.Println(" i  nᵢ  p[i,nᵢ]  c")
       fmt.Println("------------------")
   }
   if !validate {
       s = s + "0"
   }
   c := 0
   le := len(s) - 1
   for i := le; i >= 0; i-- {
       ni := int(s[i] - 48)
       pi := p[(le-i)%8][ni]
       c = d[c][pi]
       if table {
           fmt.Printf("%2d  %d      %d     %d\n", le-i, ni, pi, c)
       }
   }
   if table && !validate {
       fmt.Printf("\ninv[%d] = %d\n", c, inv[c])
   }
   if !validate {
       return inv[c]
   }
   return c == 0

}

func main() {

   ss := []string{"236", "12345", "123456789012"}
   ts := []bool{true, true, false, true}
   for i, s := range ss {
       c := verhoeff(s, false, ts[i]).(int)
       fmt.Printf("\nThe check digit for '%s' is '%d'\n\n", s, c)
       for _, sc := range []string{s + string(c+48), s + "9"} {
           v := verhoeff(sc, true, ts[i]).(bool)
           ans := "correct"
           if !v {
               ans = "incorrect"
           }
           fmt.Printf("\nThe validation for '%s' is %s\n\n", sc, ans)
       }
   }

}</lang>

Output:
Identical to Wren example

Julia

<lang julia>const multiplicationtable = [

   0  1  2  3  4  5  6  7  8  9;
   1  2  3  4  0  6  7  8  9  5;
   2  3  4  0  1  7  8  9  5  6;
   3  4  0  1  2  8  9  5  6  7;
   4  0  1  2  3  9  5  6  7  8;
   5  9  8  7  6  0  4  3  2  1;
   6  5  9  8  7  1  0  4  3  2;
   7  6  5  9  8  2  1  0  4  3;
   8  7  6  5  9  3  2  1  0  4;
   9  8  7  6  5  4  3  2  1  0]

const permutationtable = [

   0  1  2  3  4  5  6  7  8  9;
   1  5  7  6  2  8  3  0  9  4;
   5  8  0  3  7  9  6  1  4  2;
   8  9  1  6  0  4  3  5  2  7;
   9  4  5  3  1  2  6  8  7  0;
   4  2  8  6  5  7  3  9  0  1;
   2  7  9  3  8  0  6  4  1  5;
   7  0  4  6  9  1  3  2  5  8]

const inv = [0, 4, 3, 2, 1, 5, 6, 7, 8, 9]

"""

   Calculate the Verhoeff checksum over `n`.
   If checksum mode, return the expected correct checksum digit.
   If validation mode, return true if last digit checks correctly.

""" function verhoeffchecksum(n::Integer, validate=true, verbose=false)

   verbose && println("\n", validate ? "Validation" : "Check digit",
       " calculations for '$n':\n\n", " i  nᵢ  p[i,nᵢ]  c\n------------------")
   # transform number list
   c, dig = 0, reverse(digits(validate ? n : 10 * n))
   for i in length(dig):-1:1
       ni = dig[i]
       p = permutationtable[(length(dig) - i) % 8 + 1, ni + 1]
       c = multiplicationtable[c + 1, p + 1]
       verbose && println(lpad(length(dig) - i, 2), "  $ni      $p    $c")
   end
   verbose && !validate && println("\ninv($c) = $(inv[c + 1])")
   return validate ? c == 0 : inv[c + 1]

end

println("\nThe check digit for '236' is $(verhoeffchecksum(236, false, true)).") println("\nThe validation for '2363' is ", verhoeffchecksum(2363, true, true) ?

   "correct" : "incorrect", ".")

println("\nThe validation for '2369' is ", verhoeffchecksum(2369, true, true) ?

   "correct" : "incorrect", ".")

println("\nThe check digit for '12345' is $(verhoeffchecksum(12345, false, true)).") println("\nThe validation for '123451' is ", verhoeffchecksum(123451, true, true) ?

   "correct" : "incorrect", ".")

println("\nThe validation for '123459' is ", verhoeffchecksum(123459, true, true) ?

   "correct" : "incorrect", ".")

println("\nThe check digit for '123456789012' is $(verhoeffchecksum(123456789012, false)).") println("\nThe validation for '1234567890120' is ",

   verhoeffchecksum(1234567890120, true) ? "correct." : "incorrect.")

println("\nThe validation for '1234567890129' is ",

   verhoeffchecksum(1234567890129, true) ? "correct." : "incorrect.")

</lang>

Output:

Same as Wren example.


Nim

<lang Nim>import strformat

const

 D = [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
      [1, 2, 3, 4, 0, 6, 7, 8, 9, 5],
      [2, 3, 4, 0, 1, 7, 8, 9, 5, 6],
      [3, 4, 0, 1, 2, 8, 9, 5, 6, 7],
      [4, 0, 1, 2, 3, 9, 5, 6, 7, 8],
      [5, 9, 8, 7, 6, 0, 4, 3, 2, 1],
      [6, 5, 9, 8, 7, 1, 0, 4, 3, 2],
      [7, 6, 5, 9, 8, 2, 1, 0, 4, 3],
      [8, 7, 6, 5, 9, 3, 2, 1, 0, 4],
      [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]]
 Inv = [0, 4, 3, 2, 1, 5, 6, 7, 8, 9]
 P = [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
      [1, 5, 7, 6, 2, 8, 3, 0, 9, 4],
      [5, 8, 0, 3, 7, 9, 6, 1, 4, 2],
      [8, 9, 1, 6, 0, 4, 3, 5, 2, 7],
      [9, 4, 5, 3, 1, 2, 6, 8, 7, 0],
      [4, 2, 8, 6, 5, 7, 3, 9, 0, 1],
      [2, 7, 9, 3, 8, 0, 6, 4, 1, 5],
      [7, 0, 4, 6, 9, 1, 3, 2, 5, 8]]

type Digit = 0..9

proc verhoeff[T: SomeInteger](n: T; validate, verbose = false): T =

 ## Compute or validate a check digit.
 ## Return the check digit if computation or the number with the check digit
 ## removed if validation.
 ## If not in verbose mode, an exception is raised if validation failed.
 doAssert n >= 0, "Argument must not be negative."
 # Extract digits.
 var digits: seq[Digit]
 if not validate: digits.add 0
 var val = n
 while val != 0:
   digits.add val mod 10
   val = val div 10
 if verbose:
   echo if validate: &"Check digit validation for {n}:" else: &"Check digit computation for {n}:"
   echo " i  ni  p(i, ni)  c"
 # Compute c.
 var c = 0
 for i, ni in digits:
   let p = P[i mod 8][ni]
   c = D[c][p]
   if verbose: echo &"{i:2}   {ni}     {p}      {c}"
 if validate:
   if verbose:
     let verb = if c == 0: "is" else: "is not"
     echo &"Validation {verb} successful.\n"
   elif c != 0:
     raise newException(ValueError, &"Check digit validation failed for {n}.")
   result = n div 10
 else:
   result = Inv[c]
   if verbose: echo &"The check digit for {n} is {result}.\n"


for n in [236, 12345]:

 let d = verhoeff(n, false, true)
 discard verhoeff(10 * n + d, true, true)
 discard verhoeff(10 * n + 9, true, true)

let n = 123456789012 let d = verhoeff(n) echo &"Check digit for {n} is {d}." discard verhoeff(10 * n + d, true) echo &"Check digit validation was successful for {10 * n + d}." try:

 discard verhoeff(10 * n + 9, true)

except ValueError:

 echo getCurrentExceptionMsg()</lang>
Output:
Check digit computation for 236:
 i  ni  p(i, ni)  c
 0   0     0      0
 1   6     3      3
 2   3     3      1
 3   2     1      2
The check digit for 236 is 3.

Check digit validation for 2363:
 i  ni  p(i, ni)  c
 0   3     3      3
 1   6     3      1
 2   3     3      4
 3   2     1      0
Validation is successful.

Check digit validation for 2369:
 i  ni  p(i, ni)  c
 0   9     9      9
 1   6     3      6
 2   3     3      8
 3   2     1      7
Validation is not successful.

Check digit computation for 12345:
 i  ni  p(i, ni)  c
 0   0     0      0
 1   5     8      8
 2   4     7      1
 3   3     6      7
 4   2     5      2
 5   1     2      4
The check digit for 12345 is 1.

Check digit validation for 123451:
 i  ni  p(i, ni)  c
 0   1     1      1
 1   5     8      9
 2   4     7      2
 3   3     6      8
 4   2     5      3
 5   1     2      0
Validation is successful.

Check digit validation for 123459:
 i  ni  p(i, ni)  c
 0   9     9      9
 1   5     8      1
 2   4     7      8
 3   3     6      2
 4   2     5      7
 5   1     2      5
Validation is not successful.

Check digit for 123456789012 is 0.
Check digit validation was successful for 1234567890120.
Check digit validation failed for 1234567890129.

Phix

The tables were generated in case 1-based index versions of them would help, tbh, but in the end I didn't even try that, aka start with tagset(10).

with javascript_semantics
sequence d = {tagset(9,0)},
         inv = tagset(9,0),
         p = {tagset(9,0)}
for i=1 to 4 do d = append(d,extract(d[$],{2,3,4,5,1,7,8,9,10,6})) end for
for i=5 to 8 do d = append(d,reverse(d[-4])) end for
                d = append(d,reverse(d[1]))
inv[2..5] = reverse(inv[2..5])
for i=1 to 7 do p = append(p,extract(p[$],{2,6,8,7,3,9,4,1,10,5})) end for

-- alternatively, if you prefer:
--constant d = {{0,1,2,3,4,5,6,7,8,9},
--              {1,2,3,4,0,6,7,8,9,5},
--              {2,3,4,0,1,7,8,9,5,6},
--              {3,4,0,1,2,8,9,5,6,7},
--              {4,0,1,2,3,9,5,6,7,8},
--              {5,9,8,7,6,0,4,3,2,1},
--              {6,5,9,8,7,1,0,4,3,2},
--              {7,6,5,9,8,2,1,0,4,3},
--              {8,7,6,5,9,3,2,1,0,4},
--              {9,8,7,6,5,4,3,2,1,0}},
--        inv = {0,4,3,2,1,5,6,7,8,9},
--         p = {{0,1,2,3,4,5,6,7,8,9},
--              {1,5,7,6,2,8,3,0,9,4},
--              {5,8,0,3,7,9,6,1,4,2},
--              {8,9,1,6,0,4,3,5,2,7},
--              {9,4,5,3,1,2,6,8,7,0},
--              {4,2,8,6,5,7,3,9,0,1},
--              {2,7,9,3,8,0,6,4,1,5},
--              {7,0,4,6,9,1,3,2,5,8}}

function verhoeff(string n, bool validate=false, show_workings=false)
    string {s,t} = iff(validate?{n,"Validation"}:{n&'0',"Check digit"})
    if show_workings then
        printf(1,"%s calculations for `%s`:\n", {t, n})
        printf(1," i  ni  p(i,ni)  c\n")
        printf(1,"------------------\n")
    end if
    integer c = 0
    for i=1 to length(s) do
        integer ni = s[-i]-'0',
                pi = p[remainder(i-1,8)+1][ni+1]
        c = d[c+1][pi+1]
        if show_workings then
            printf(1,"%2d  %d      %d     %d\n", {i-1, ni, pi, c})
        end if
    end for
    integer ch = inv[c+1]+'0'
    string r = iff(validate?iff(c=0?"":"in")&"correct"
                           :"`"&ch&"`")
    printf(1,"The %s for `%s` is %s\n\n",{lower(t),n,r})
    return ch
end function

constant tests = {"236", "12345", "123456789012"}
for i=1 to length(tests) do
    bool show_workings = (i<=2)
    integer ch = verhoeff(tests[i],false,show_workings)
    assert(verhoeff(tests[i]&ch,true,show_workings)=='0')
    assert(verhoeff(tests[i]&'9',true,show_workings)!='0')
end for
Output:
Check digit calculations for `236`:
 i  ni  p(i,ni)  c
------------------
 0  0      0     0
 1  6      3     3
 2  3      3     1
 3  2      1     2
The check digit for `236` is `3`

Validation calculations for `2363`:
 i  ni  p(i,ni)  c
------------------
 0  3      3     3
 1  6      3     1
 2  3      3     4
 3  2      1     0
The validation for `2363` is correct

Validation calculations for `2369`:
 i  ni  p(i,ni)  c
------------------
 0  9      9     9
 1  6      3     6
 2  3      3     8
 3  2      1     7
The validation for `2369` is incorrect

Check digit calculations for `12345`:
 i  ni  p(i,ni)  c
------------------
 0  0      0     0
 1  5      8     8
 2  4      7     1
 3  3      6     7
 4  2      5     2
 5  1      2     4
The check digit for `12345` is `1`

Validation calculations for `123451`:
 i  ni  p(i,ni)  c
------------------
 0  1      1     1
 1  5      8     9
 2  4      7     2
 3  3      6     8
 4  2      5     3
 5  1      2     0
The validation for `123451` is correct

Validation calculations for `123459`:
 i  ni  p(i,ni)  c
------------------
 0  9      9     9
 1  5      8     1
 2  4      7     8
 3  3      6     2
 4  2      5     7
 5  1      2     5
The validation for `123459` is incorrect

The check digit for `123456789012` is `0`

The validation for `1234567890120` is correct

The validation for `1234567890129` is incorrect

Raku

Generate the tables rather than hard coding, They're not all that complex.

<lang perl6>my @d = [^10] xx 5; @d[$_][^5].=rotate($_), @d[$_][5..*].=rotate($_) for 1..4; push @d: [@d[$_].reverse] for flat 1..4, 0;

my @i = 0,4,3,2,1,5,6,7,8,9;

my %h = flat (0,1,5,8,9,4,2,7,0).rotor(2 =>-1).map({.[0]=>.[1]}), 6=>3, 3=>6; my @p = [^10],; @p.push: [@p[*-1].map: {%h{$_}}] for ^7;

sub checksum (Int $int where * ≥ 0, :$verbose = True ) {

   my @digits = $int.comb;
   say "\nCheckdigit calculation for $int:";
   say " i  ni  p(i, ni)  c" if $verbose;
   my ($i, $p, $c) = 0 xx 3;
   say " $i   0      $p     $c" if $verbose;
   for @digits.reverse {
       ++$i;
       $p = @p[$i % 8][$_];
       $c = @d[$c; $p];
       say "{$i.fmt('%2d')}   $_      $p     $c" if $verbose;
   }
   say "Checkdigit: {@i[$c]}";
   +($int ~ @i[$c]);

}

sub validate (Int $int where * ≥ 0, :$verbose = True) {

   my @digits = $int.comb;
   say "\nValidation calculation for $int:";
   say " i  ni  p(i, ni)  c" if $verbose;
   my ($i, $p, $c) = 0 xx 3;
   for @digits.reverse {
       $p = @p[$i % 8][$_];
       $c = @d[$c; $p];
       say "{$i.fmt('%2d')}   $_      $p     $c" if $verbose;
       ++$i;
   }
   say "Checkdigit: {'in' if $c}correct";

}

    1. TESTING

for 236, 12345, 123456789012 -> $int {

   my $check = checksum $int, :verbose( $int.chars < 8 );
   validate $check, :verbose( $int.chars < 8 );
   validate +($check.chop ~ 9), :verbose( $int.chars < 8 );

}</lang>

Output:
Checkdigit calculation for 236:
 i  ni  p(i, ni)  c
 0   0      0     0
 1   6      3     3
 2   3      3     1
 3   2      1     2
Checkdigit: 3

Validation calculation for 2363:
 i  ni  p(i, ni)  c
 0   3      3     3
 1   6      3     1
 2   3      3     4
 3   2      1     0
Checkdigit: correct

Validation calculation for 2369:
 i  ni  p(i, ni)  c
 0   9      9     9
 1   6      3     6
 2   3      3     8
 3   2      1     7
Checkdigit: incorrect

Checkdigit calculation for 12345:
 i  ni  p(i, ni)  c
 0   0      0     0
 1   5      8     8
 2   4      7     1
 3   3      6     7
 4   2      5     2
 5   1      2     4
Checkdigit: 1

Validation calculation for 123451:
 i  ni  p(i, ni)  c
 0   1      1     1
 1   5      8     9
 2   4      7     2
 3   3      6     8
 4   2      5     3
 5   1      2     0
Checkdigit: correct

Validation calculation for 123459:
 i  ni  p(i, ni)  c
 0   9      9     9
 1   5      8     1
 2   4      7     8
 3   3      6     2
 4   2      5     7
 5   1      2     5
Checkdigit: incorrect

Checkdigit calculation for 123456789012:
Checkdigit: 0

Validation calculation for 1234567890120:
Checkdigit: correct

Validation calculation for 1234567890129:
Checkdigit: incorrect

Wren

Library: Wren-fmt

<lang ecmascript>import "/fmt" for Fmt

var d = [

   [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
   [1, 2, 3, 4, 0, 6, 7, 8, 9, 5],
   [2, 3, 4, 0, 1, 7, 8, 9, 5, 6],
   [3, 4, 0, 1, 2, 8, 9, 5, 6, 7],
   [4, 0, 1, 2, 3, 9, 5, 6, 7, 8],
   [5, 9, 8, 7, 6, 0, 4, 3, 2, 1],
   [6, 5, 9, 8, 7, 1, 0, 4, 3, 2],
   [7, 6, 5, 9, 8, 2, 1, 0, 4, 3],
   [8, 7, 6, 5, 9, 3, 2, 1, 0, 4],
   [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

]

var inv = [0, 4, 3, 2, 1, 5, 6, 7, 8, 9]

var p = [

   [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
   [1, 5, 7, 6, 2, 8, 3, 0, 9, 4],
   [5, 8, 0, 3, 7, 9, 6, 1, 4, 2],
   [8, 9, 1, 6, 0, 4, 3, 5, 2, 7],
   [9, 4, 5, 3, 1, 2, 6, 8, 7, 0],
   [4, 2, 8, 6, 5, 7, 3, 9, 0, 1],
   [2, 7, 9, 3, 8, 0, 6, 4, 1, 5],
   [7, 0, 4, 6, 9, 1, 3, 2, 5, 8]

]

var verhoeff = Fn.new { |s, validate, table|

   if (table) {
       System.print("%(validate ? "Validation" : "Check digit") calculations for '%(s)':\n")
       System.print(" i  nᵢ  p[i,nᵢ]  c")
       System.print("------------------")
   }
   if (!validate) s = s + "0"
   var c = 0
   var le = s.count - 1
   for (i in le..0) {
       var ni = s[i].bytes[0] - 48
       var pi = p[(le-i) % 8][ni]
       c = d[c][pi]
       if (table) Fmt.print("$2d  $d      $d     $d", le-i, ni, pi, c)           
   }
   if (table && !validate) System.print("\ninv[%(c)] = %(inv[c])")
   return !validate ? inv[c] : c == 0

}

var sts = [["236", true], ["12345", true], ["123456789012", false]] for (st in sts) {

   var c = verhoeff.call(st[0], false, st[1])
   System.print("\nThe check digit for '%(st[0])' is '%(c)'\n")
   for (stc in [st[0] + c.toString, st[0] + "9"]) {
       var v = verhoeff.call(stc, true, st[1])
       System.print("\nThe validation for '%(stc)' is %(v ? "correct" : "incorrect").\n")
   }

}</lang>

Output:
Check digit calculations for '236':

 i  nᵢ  p[i,nᵢ]  c
------------------
 0  0      0     0
 1  6      3     3
 2  3      3     1
 3  2      1     2

inv[2] = 3

The check digit for '236' is '3'

Validation calculations for '2363':

 i  nᵢ  p[i,nᵢ]  c
------------------
 0  3      3     3
 1  6      3     1
 2  3      3     4
 3  2      1     0

The validation for '2363' is correct.

Validation calculations for '2369':

 i  nᵢ  p[i,nᵢ]  c
------------------
 0  9      9     9
 1  6      3     6
 2  3      3     8
 3  2      1     7

The validation for '2369' is incorrect.

Check digit calculations for '12345':

 i  nᵢ  p[i,nᵢ]  c
------------------
 0  0      0     0
 1  5      8     8
 2  4      7     1
 3  3      6     7
 4  2      5     2
 5  1      2     4

inv[4] = 1

The check digit for '12345' is '1'

Validation calculations for '123451':

 i  nᵢ  p[i,nᵢ]  c
------------------
 0  1      1     1
 1  5      8     9
 2  4      7     2
 3  3      6     8
 4  2      5     3
 5  1      2     0

The validation for '123451' is correct.

Validation calculations for '123459':

 i  nᵢ  p[i,nᵢ]  c
------------------
 0  9      9     9
 1  5      8     1
 2  4      7     8
 3  3      6     2
 4  2      5     7
 5  1      2     5

The validation for '123459' is incorrect.


The check digit for '123456789012' is '0'


The validation for '1234567890120' is correct.


The validation for '1234567890129' is incorrect.