Algorytm Luhna mod N
Algorytm Luhna mod N bazie jest rozszerzeniem algorytmu Luhna ( znanego również jako algorytm mod 10), które umożliwia mu pracę z sekwencjami wartości w dowolnej parzystej . Może to być przydatne, gdy cyfra kontrolna jest wymagana do sprawdzenia poprawności ciągu identyfikacyjnego złożonego z liter, kombinacji liter i cyfr lub dowolnego zestawu N znaków, gdzie N jest podzielne przez 2.
Nieformalne wyjaśnienie
Luhna mod N generuje cyfrę kontrolną (dokładniej znak kontrolny) w tym samym zakresie prawidłowych znaków, co ciąg wejściowy. Na przykład, jeśli algorytm zostanie zastosowany do ciągu małych liter ( a do z ), znak kontrolny będzie również małą literą. Poza tym rozróżnieniem bardzo przypomina oryginalny algorytm.
Główną ideą tego rozszerzenia jest to, że pełny zestaw prawidłowych znaków wejściowych jest odwzorowywany na listę punktów kodowych (tj. kolejnych liczb całkowitych zaczynających się od zera). Algorytm przetwarza łańcuch wejściowy, konwertując każdy znak na powiązany z nim punkt kodowy, a następnie wykonując obliczenia w mod N (gdzie N to liczba prawidłowych znaków wejściowych). Na koniec wynikowy punkt kodowy kontroli jest odwzorowywany z powrotem w celu uzyskania odpowiadającego mu znaku kontrolnego.
Ograniczenie
Algorytm Luhna mod N działa tylko wtedy, gdy N jest podzielne przez 2. Dzieje się tak, ponieważ istnieje operacja korygowania wartości pozycji po podwojeniu jej wartości, która nie działa, gdy N nie jest podzielne przez 2. Dla aplikacji korzystających z alfabetu angielskiego nie stanowi to problemu, ponieważ ciąg małych liter ma 26 punktów kodowych, a dodanie znaków dziesiętnych dodaje kolejne 10, zachowując N podzielne przez 2.
Wyjaśnienie
Drugi krok w algorytmie Luhna przepakowuje podwojoną wartość pozycji do podstawy oryginalnej cyfry , dodając do siebie poszczególne cyfry podwojonej wartości, gdy są zapisane w podstawie N . Ten krok skutkuje liczbami parzystymi, jeśli podwojona wartość jest mniejsza lub równa N , i liczbami nieparzystymi, jeśli podwojona wartość jest większa niż N . Na przykład w aplikacjach dziesiętnych , w których N wynosi 10, oryginalne wartości między 0 a 4 dają liczby parzyste, a oryginalne wartości między 5 a 9 dają liczby nieparzyste, skutecznie przepakowując podwojone wartości między 0 a 18 w jeden odrębny wynik między 0 a 9.
Gdy używane jest N , które nie jest podzielne przez 2, ten krok zwraca liczby parzyste dla podwojonych wartości większych niż N , których nie można odróżnić od podwojonych wartości mniejszych lub równych N .
Wynik
Algorytm nie wykryje ani wszystkich błędów jednocyfrowych, ani wszystkich transpozycji sąsiednich cyfr, jeśli zostanie użyte N , które nie jest podzielne przez 2. Ponieważ te możliwości wykrywania są głównymi zaletami algorytmu, algorytm jest prawie całkowicie osłabiony przez to ograniczenie. Nieparzysta odmiana algorytmu Luhna mod N umożliwia zastosowania, w których N nie jest podzielne przez 2, poprzez zastąpienie podwojonej wartości w każdej pozycji resztą po podzieleniu wartości pozycji przez N , co daje reszty nieparzyste zgodne z oryginalnym projektem algorytmu.
Odwzorowywanie znaków na punkty kodowe
Początkowo należy utworzyć mapowanie między prawidłowymi znakami wejściowymi a punktami kodowymi. Weźmy na przykład pod uwagę, że prawidłowe znaki to małe litery od a do f . Dlatego odpowiednim odwzorowaniem byłoby:
Postać | A | B | C | D | mi | F |
---|---|---|---|---|---|---|
Punkt kodowy | 0 | 1 | 2 | 3 | 4 | 5 |
Pamiętaj, że kolejność znaków jest całkowicie nieistotna. To inne mapowanie również byłoby akceptowalne (choć być może bardziej kłopotliwe w implementacji):
Postać | C | mi | A | F | B | D |
---|---|---|---|---|---|---|
Punkt kodowy | 0 | 1 | 2 | 3 | 4 | 5 |
Możliwe jest również mieszanie liter i cyfr (a być może nawet innych znaków). Na przykład to odwzorowanie byłoby odpowiednie dla małych cyfr szesnastkowych:
Postać | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | mi | F |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Punkt kodowy | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
Algorytm w C#
Zakładając, że zdefiniowane są następujące funkcje:
int CodePointFromCharacter ( char znak ) {...} char CharacterFromCodePoint ( int codePoint ) {...} int NumberOfValidInputCharacters () {...}
Funkcja generująca znak kontrolny to:
0
0 char GenerateCheckCharacter ( string input ) { int factor = 2 ; int suma = ; int n = liczba prawidłowych znaków wejściowych (); // Rozpoczęcie od prawej strony i praca w lewo jest łatwiejsze, ponieważ // początkowy "współczynnik" zawsze będzie wynosił "2". for ( int i = wejście . Długość - 1 ; i >= ;
i --) { int codePoint = CodePointFromCharacter ( input [ i ]); int addend = czynnik * codePoint ; // Zamień „czynnik”, że każdy „punkt kodowy” jest mnożony przez factor = ( factor == 2 ) ? 1 : 2 ; // Zsumuj cyfry „addend” wyrażone w podstawie „n” addend =
IntegerValue ( addend / n ) + ( addend % n ); suma += dodaj ; } // Oblicz liczbę, którą należy dodać do "sumy" // aby była podzielna przez "n". int reszta = suma % n ; int checkCodePoint = ( n - reszta ) % n ; powrót
ZnakOdPunktuKodowego ( sprawdzaniePunktuKodowego ); }
A funkcja sprawdzania poprawności łańcucha (ze znakiem kontrolnym jako ostatnim znakiem) to:
0
bool ValidateCheckCharacter ( ciąg wejściowy ) { współczynnik int = 1 ; int suma = ; int n = liczba prawidłowych znaków wejściowych (); // Rozpoczynając od prawej strony, pracuj w lewo // Teraz początkowym „współczynnikiem” będzie zawsze „1” // ponieważ ostatnim znakiem jest znak kontrolny. for ( int i = wejście . Długość - 1 ; 0
ja >= ; i --) { int codePoint = CodePointFromCharacter ( input [ i ]); int addend = czynnik * codePoint ; // Zamień „czynnik”, że każdy „punkt kodowy” jest mnożony przez factor = ( factor == 2 ) ? 1 : 2 ;
0
// Zsumuj cyfry „addend” wyrażone w podstawie „n” addend = IntegerValue ( addend / n ) + ( addend % n ); suma += dodaj ; } int reszta = suma % n ; powrót ( reszta == ); }
Algorytm w Javie
Zakładając, że zdefiniowane są następujące funkcje:
int codePointFromCharacter ( char znak ) {...} char characterFromCodePoint ( int codePoint ) {...} int numberOfValidInputCharacters () {...}
Funkcja generująca znak kontrolny to:
0
char generujCheckCharacter ( ciąg znaków wejściowych ) { współczynnik int = 2 ; int suma = ; int n = liczba prawidłowych znaków wejściowych (); // Rozpoczęcie od prawej strony i praca w lewo jest łatwiejsze, ponieważ // początkowy "współczynnik" zawsze będzie wynosił "2". for ( int i = input . length () - 1 ; i >= 0
; i -- ) { int codePoint = codePointFromCharacter ( input . charAt ( i )); int addend = czynnik * codePoint ; // Zamień „czynnik”, że każdy „punkt kodowy” jest mnożony przez factor = ( factor == 2 ) ? 1 : 2 ;
// Zsumuj cyfry „addend” wyrażone w podstawie „n” addend = ( addend / n ) + ( addend % n ); suma += dodaj ; } // Oblicz liczbę, którą należy dodać do "sumy" // aby była podzielna przez "n". int reszta = suma % n ; int checkCodePoint = ( n - reszta
) % n ; zwraca znakOdPunktuKodowego ( sprawdźPunktKodowy ); }
A funkcja sprawdzania poprawności łańcucha (ze znakiem kontrolnym jako ostatnim znakiem) to:
0
boolean validateCheckCharacter ( ciąg znaków wejściowych ) { int factor = 1 ; int suma = ; int n = liczba prawidłowych znaków wejściowych (); // Zaczynając od prawej strony, pracuj w lewo // Teraz początkowy „czynnik” zawsze będzie wynosił „1” // ponieważ ostatni znak to znak kontrolny. for ( int i = wejście . długość () - 0
1 ; ja >= ; i -- ) { int codePoint = codePointFromCharacter ( input . charAt ( i )); int addend = czynnik * codePoint ; // Zamień „czynnik”, że każdy „punkt kodowy” jest mnożony przez factor = ( factor == 2 ) ? 1 : 2 ;
0
// Zsumuj cyfry „addend” wyrażone w podstawie „n” addend = ( addend / n ) + ( addend % n ); suma += dodaj ; } int reszta = suma % n ; powrót ( reszta == ); }
Algorytm w JavaScript
Zakładając, że zdefiniowane są następujące funkcje:
const codePoints = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" ; //Może to być dowolny ciąg dozwolonych znaków function numberOfValidInputCharacters () { return codePoints . długość ; } function codePointFromCharacter ( znak ) { zwraca codePoints . indexOf ( znak ); } funkcja znakOdPunktuKodowego (
codePoint ) { zwraca codePoints . charAt ( punkt kodowy ); }
Funkcja generująca znak kontrolny to:
0
0 funkcja generujCheckCharacter ( input ) { niech factor = 2 ; niech suma = ; niech n = liczbaPrawidłowychZnakówWejścia (); // Rozpoczęcie od prawej strony i praca w lewo jest łatwiejsze, ponieważ // początkowy "współczynnik" zawsze będzie wynosił "2". for ( niech i = wejście . długość - 1 ; i >= ; i
-- ) { niech codePoint = codePointFromCharacter ( input . charAt ( i )); niech addend = factor * codePoint ; // Zamień „czynnik”, że każdy „punkt kodowy” jest mnożony przez factor = ( factor == 2 ) ? 1 : 2 ; // Zsumuj cyfry „dodaj” wyrażone w podstawie „n”
addend = ( Math . floor ( addend / n )) + ( addend % n ); suma += dodaj ; } // Oblicz liczbę, którą należy dodać do "sumy" // aby była podzielna przez "n". niech reszta = suma % n ; niech checkCodePoint = ( n - reszta ) %
n ; zwraca znakOdPunktuKodowego ( sprawdźPunktKodowy ); }
A funkcja sprawdzania poprawności łańcucha (ze znakiem kontrolnym jako ostatnim znakiem) to:
0
funkcja validateCheckCharacter ( input ) { niech factor = 1 ; niech suma = ; niech n = liczbaPrawidłowychZnakówWejścia (); // Rozpoczynając od prawej strony, pracuj w lewo // Teraz początkowym „współczynnikiem” będzie zawsze „1” // ponieważ ostatnim znakiem jest znak kontrolny. for ( niech i = wejście . długość - 1 ; i 0
>= ; i -- ) { niech codePoint = codePointFromCharacter ( input . charAt ( i )); niech addend = czynnik * codePoint ; // Zamienić „czynnik”, że każdy „punkt kodowy” jest mnożony przez factor = ( factor == 2 ) ? 1 : 2 ;
0
// Zsumuj cyfry „addend” wyrażone w podstawie „n” addend = ( Math . floor ( addend / n )) + ( addend % n ); suma += dodaj ; } niech reszta = suma % n ; powrót ( reszta == ); }
Przykład
Pokolenie
Rozważ powyższy zestaw prawidłowych znaków wejściowych i przykładowy ciąg wejściowy abcdef . Aby wygenerować znak kontrolny, zacznij od ostatniego znaku w łańcuchu i przesuń w lewo podwajając każdy inny punkt kodowy. „Cyfry” punktów kodowych zapisane w podstawie 6 (ponieważ istnieje 6 prawidłowych znaków wejściowych) należy następnie zsumować:
Postać | A | B | C | D | mi | F |
---|---|---|---|---|---|---|
Punkt kodowy | 0 | 1 | 2 | 3 | 4 | 5 |
Podwójnie | 2 |
6 (podstawa 10) 10 (podstawa 6) |
10 (podstawa 10) 14 (podstawa 6) |
|||
Zmniejszyć | 0 | 2 | 2 | 1 + 0 | 4 | 1 + 4 |
Suma cyfr | 0 | 2 | 2 | 1 | 4 | 5 |
Suma cyfr wynosi 14 (0 + 2 + 2 + 1 + 4 + 5). Liczba, którą należy dodać, aby otrzymać kolejną wielokrotność 6 (w tym przypadku 18 ) to 4 . To jest wynikowy punkt kodowy kontroli. Powiązanym znakiem kontrolnym jest e .
Walidacja
Wynikowy łańcuch abcdefe można następnie zweryfikować za pomocą podobnej procedury:
Postać | A | B | C | D | mi | F | mi |
---|---|---|---|---|---|---|---|
Punkt kodowy | 0 | 1 | 2 | 3 | 4 | 5 | 4 |
Podwójnie | 2 |
6 (podstawa 10) 10 (podstawa 6) |
10 (podstawa 10) 14 (podstawa 6) |
||||
Zmniejszyć | 0 | 2 | 2 | 1 + 0 | 4 | 1 + 4 | 4 |
Suma cyfr | 0 | 2 | 2 | 1 | 4 | 5 | 4 |
Suma cyfr wynosi 18 . Ponieważ jest podzielna przez 6, znak kontrolny jest ważny .
Realizacja
0 Odwzorowanie znaków na punkty kodowe iz powrotem można zaimplementować na wiele sposobów. Najprostszym podejściem (podobnym do oryginalnego algorytmu Luhna) jest użycie arytmetyki kodu ASCII. Na przykład, mając dane wejściowe ustawione na 9 , punkt kodowy można obliczyć, odejmując kod ASCII dla „0” od kodu ASCII żądanego znaku. Operacja odwrotna zapewni odwzorowanie odwrotne. Dodatkowe zakresy znaków można obsłużyć za pomocą instrukcji warunkowych.
Zestawy niesekwencyjne można mapować w obie strony za pomocą zakodowanej na stałe instrukcji switch/case . Bardziej elastycznym podejściem jest użycie czegoś podobnego do tablicy asocjacyjnej . Aby to zadziałało, wymagana jest para tablic, aby zapewnić dwukierunkowe mapowanie.
Dodatkową możliwością jest użycie tablicy znaków, gdzie indeksy tablicy są punktami kodowymi powiązanymi z każdym znakiem. Odwzorowanie znaku na punkt kodowy można następnie przeprowadzić za pomocą wyszukiwania liniowego lub binarnego. W tym przypadku odwrotne mapowanie jest po prostu prostym wyszukiwaniem w tablicy.
Słabość
0 To rozszerzenie ma tę samą słabość, co oryginalny algorytm, a mianowicie nie może wykryć transpozycji sekwencji <pierwszy-ważny-znak><ostatni-ważny-znak> na <ostatni-ważny-znak><pierwszy-ważny-znak> (lub odwrotnie). Jest to równoważne transpozycji 09 do 90 (zakładając zestaw prawidłowych znaków wejściowych od do 9 w kolejności). Z pozytywnego punktu widzenia, im większy zestaw prawidłowych znaków wejściowych, tym mniejszy wpływ słabości.