A recent question about credit card validation here at Code Review led me into a dark hole with check digit algorithms. I stopped at the Verhoeff algorithm and tried to implement it myself.

This resulted in the following code:

```
Class Verhoeff:
Calculate and check the check digits with the Verhoeff algorithm.
MULTIPLICATION_TABLE = (
(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)
)
INVERSE_TABLE = (0, 4, 3, 2, 1, 5, 6, 7, 8, 9)
PERMUTATION_TABLE = (
(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)
)
@classmethod
def compute (cls, input_: str) -> str:
Calculate the check digit with the Verhoeff algorithm.
check_digit = 0
for i digit in enumeration (inverse (input_), 1):
col_idx = cls.PERMUTATION_TABLE[i % 8][int(digit)]
check_digit = cls.MULTIPLICATION_TABLE[check_digit][col_idx]
return str (cls.INVERSE_TABLE[check_digit])
@classmethod
def validate (cls, input_: str) -> bool:
Check the check digit with the Verhoeff algorithm.
check_digit = 0
for i digit in enumeration (inverse (input_)):
col_idx = cls.PERMUTATION_TABLE[i % 8][int(digit)]
check_digit = cls.MULTIPLICATION_TABLE[check_digit][col_idx]
Return cls.INVERSE_TABLE[check_digit] == 0
```

I decided to implement it as a class with two class methods, as I intend to include other algorithms, and structuring the code in this way seemed reasonable to me.

Your feedback on the following aspects is of particular interest to me:

- What do you think about the API?
`calculate (input_: str) -> str`

and `validate (input_: str) -> bool`

reasonable and symmetrical, but I could also use something like picture `calculate (input_: sequence[int]) -> int`

/`validate (input_: Sequence[int], int) -> bool`

,
- There seems to be a reasonable amount of code duplication between the two functions
`calculation`

/`to confirm`

but I could not really put my hand over how to define one in relation to the other.

In addition to the above class, I've decided to do some unit testing on the algorithm with pytest.

```
Import string
import itertools
import pytest
from check_sums import Luhn, Damm, Verhoeff
# Modifiers and utility functions to test the robustness of the check digit algorithm
DIGIT_REPLACEMENTS = {
digit: string.digits.replace (digit, "") for digit in string.digits
}
def single_digit_modifications (input_):
Generate all single-digit modifications of a numeric input sequence
for i digit in enumeration (input_):
to replace in DIGIT_REPLACEMENTS[digit]:
Yield Input_[:i] + replacement + input_[i+1:]
def transposition_modifications (input_):
"" "Pairwise transposition of all adjacent digits
The algorithm tries to make sure that the transpositions really change
Entrance. This is done to make sure that these permutations actually change that
Entrance."""
for i number in list (input_[:-1]):
if numeral! = Input_[i+1]:
Yield Input_[:i] + input_[i+1] + digit + input_[i+2:]
def flattening (iterable_of_iterables):
"" "Flatten a nesting level
Borrowed from
https://docs.python.org/3/library/itertools.html#itertools-recipes
"" "
return itertools.chain.from_iterable (iterable_of_iterables)
# Verhoeff algoritm related tests
# Test data from
# https://en.wikibooks.org/wiki/Algorithm_Implementation/Checksums/Verhoeff_Algorithm
VALID_VERHOEF_INPUTS = [
"2363", "758722", "123451", "1428570", "1234567890120",
"84736430954837284567892"
]
@pytest.mark.parametrize ("input_", VALID_VERHOEF_INPUTS)
def test_verhoeff_calculate_validate (input_):
Test Verhoeff.calculate / Verhoeff.validate with known valid entries
claim Verhoeff.calculate (input_[:-1]) == input_[-1]
and Verhoeff.validate (input_)
@pytest.mark.parametrize (
"Modified_input"
flatten (single_digit_modifications (i) for i in VALID_VERHOEF_INPUTS)
)
def test_verhoeff_single_digit_modifications (modified_input):
Test if single-digit changes can be detected
do not insure Verhoeff.validate (modified_input)
@pytest.mark.parametrize (
"Modified_input"
Flattening (Transposition_Modifications (i) for i in VALID_VERHOEF_INPUTS)
)
def test_verhoeff_transposition_modifications (modified_input):
Test if transposition changes can be detected
do not insure Verhoeff.validate (modified_input)
```

The tests cover known pre-calculated input and check digit values as well as some of the basic error classes (single-digit errors, transpositions) that the checksum was designed to detect. I've decided to generate all the modified input in the test fixture to make it easier to see which of the modified inputs is causing the algorithm to fail. So far I have not found any.