Gyakorlati alapok II.

Nyilvános adatok a szupertitkos személyi számból

 

A hangzatos, meglehet kissé fellengősre sikerült alcím egy olyan projektet fémjelez, amely talán az első "éles", a való életből vett, modellező feladat a honlapon: egy személyi szám ellenőrző alkalmazás megírása.

 

A személyi szám belső szerkezetéről egyedül a Wikipédián találtam információkat, egyedüli implementációs kiindulópont ez volt.

 

www.informatika-programozas.hu - Ezt most meg kell tanulni!

 

Tehát szeretném hangsúlyozni, hogy nincsenek róla további adataim, csak amennyi a linkelt oldalon elérhető. Később fogjuk látni, hogy emiatt lesznek implementációs korlátok, hiszen az egzakt, minden lehetséges körülményre kiterjedő programozást rendkívül részletes specifikációkészítésnek kell megelőznie.

 

Ám programozási gyakorlatnak ennyi információ is megteszi.

 

Először nézzük meg, hogy mit mond a személyi számról a Wikipédia (majdnem szó szerinti idézet):

"A személyi szám úgynevezett beszélő szám, azaz belső struktúrája van. 11 decimális számjegyből áll és M-ÉÉHHNN-SSSK alakú:

A K-val jelölt utolsó számjegy egy matematikai művelet eredménye:

 

K = (1*M + 2*É + 3*É + 4*H + 5*H + 6*N + 7*N + 8*S + 9*S + 10*S) mod 11


Más szóval az első számjegyet megszorozzuk 1-gyel, a másodikat 2-vel s így tovább 10-ig. A szorzatokat összeadjuk és az eredményt elosztjuk 11-gyel. A maradék lesz a 11. számjegy.
A maradék értéke matematikailag lehetne 10 is, viszont az ilyen személyi számokat a jogszabály szerint nem szabad kiosztani senkinek. (Hasonló szituációban az ISBN-ben az X karakter jelenti a 10-et.)
Az ellenőrző számjegy használatával kimutatható, ha egy számjegyet elír valaki, vagy tévedésből felcserél két számjegyet. Kivéve, ha a két utolsó számjegy cserélődik meg! Ez esetben ugyanis az így kapott személyi szám átmegy az ellenőrzésen. Éppen ezért az algoritmust az 1996. december 31. után születetteknél megváltoztatták:

 

K = (10*M + 9*É + 8*É + 7*H + 6*H + 5*N + 4*N + 3*S + 2*S + 1*S) mod 11


Ha egy személyi szám érvényességét akarjuk ellenőrizni, az új algoritmust kell használnunk, ha az első jegy 1, 2, 5 vagy 6, és a dátumrész 970101 és 991231 között van; a régi algoritmust, ha az első jegy 7 vagy 8; és mindkettőt, ha az első jegy 3 vagy 4 (hiszen a születési év lehet 18xx is, de lehet 20xx is)."

 

Adott tehát voltaképpen 3 releváns információ:

  1. M-tábla (lásd alább),

  2. 2 db képlet: 1 régi és 1 új,

  3. kiértékelés (az idézet utolsó 4 sora).

Először nézzük meg az M-táblát:

 

www.informatika-programozas.hu

Forrás - Source: Wikipédia

 

A táblázat felhasználásával megállapíthatjuk, hogy milyen kizárólagos bemeneti adatokat várunk el a felhasználótól: 11 db numerikus formátumot, még pedig csakis egyjegyű egész számot. Programozás-technikailag lehet ez char, String vagy int típus, esetleg mindhárom (futás közbeni konvertálással).

 

1. lépés

1. lépésben el kell döntenünk, hogy mi legyen a bemeneti adattípus. Én egy 11 elemű, int típusú elem mellett döntöttem (int[] szemSzamTomb) ), amelynek elemeit igény szerint tovább tudjuk konvertálni és jól tudjuk ellenőrizni. (De azért ne felejtsük el, hogy a számok egy része dátumokat jelöl. Erről majd később.) Ám a közvetlen adatbekéréskor még String típusként kérjük be (static int[] adatBekeresEllenorzes()), ekkor ellenőrizzük, hogy csakis egyjegyűek legyenek és 0 1 2 3 4 5 6 7 8 9 karakterek valamelyike. Ha a 2 feltétel egyidejűleg teljesül, csakis akkor indulhat a String int konvertálás, ezáltal kerülve el a numberFormatException kivétel bekövetkeztét. Ez egy durva fordítási hiba, amely során a JVM azt érzékeli, hogy a bemeneti adat mégsem szám, azaz nem konvertálható numerikus adattá. Ekkor a program futása megszakad, sőt még a takarítónő is ordítva dobja el partvisát és rohan ki a szobából.

 

2. lépés

2. lépésben tovább szűrhetjük a bemeneti adatokat.

 

www.informatika-programozas.hu - Ezt most meg kell tanulni!

 

Ám fontos hangsúlyozni, hogy ez teljesen felesleges lépés, mert a személyi számba implementált validátor-érvényesítő matematikai képletek pontosan erre valók, sőt lényegesen jobb a hibadetektálásuk. Ebből következően a 2. lépés tulajdonképpen csak egy programozási ujjgyakorlat (amely azért redundánsan beépíthető a kódba).

 

A személyi szám első 7 digitének (értelmezés a Digit vagy helyiérték? című fejezetben) lehetséges értékei az alábbiak lehetnek:

  1. digitében lehet (M): 1-2-3-4-5-6-7-8

  2. digitében lehet  (születési év 3. digit): 0-1-2-3-4-5-6-7-8-9

  3. digitében lehet  (születési év 4. digit): 0-1-2-3-4-5-6-7-8-9

  4. digitében lehet  (hónap 1. digit): 0-1

  5. digitében lehet  (hónap 2. digit): 1-2-3-4-5-6-7-8-9

  6. digitében lehet  (nap 1. digit): 0-1-2-3

  7. digitében lehet  (nap 2. digit):  0-1-2-3-4-5-6-7-8-9

Vegyük észre, hogy nem kell ellenőrizni a 2., 3., 7. digitet, hiszen annak tartománya már szűrve lett az adatBekeresEllenorzes() függvényben. A 2. körös ellenőrzést a következő függvények hajtják végre:

 

static boolean isValidSzemelySzam(int[] tomb){
    boolean ok = true;
    ok = isValidPozicio12345678(tomb[0]);
    ok = isValidPozicio01(tomb[3]);
    ok = isValidPozicio123456789(tomb[4]);
    ok = isValidPozicio0123(tomb[5]);
    return ok;
}

static boolean isValidPozicio12345678(int pozicio){
    boolean ok = true;
    if(pozicio < 1 || pozicio > 8){
    ok = false;
    }
    return ok;
}

static boolean isValidPozicio123456789(int pozicio){
    boolean ok = true;
    if(pozicio < 1 || pozicio > 9){
    ok = false;
    }
    return ok;
}

static boolean isValidPozicio01(int pozicio){
    boolean ok = true;
    if(pozicio != 0 && pozicio != 1){
    ok = false;
    }
    return ok;
}

static boolean isValidPozicio0123(int pozicio){
    boolean ok = true;
    if(pozicio != 0 && pozicio != 1 && pozicio != 2 && pozicio != 3){
    ok = false;
    }
    return ok;
}

 

A szűrés bár jól üzemel, de mégsem ad teljes védelmet, mert:

www.informatika-programozas.hu - Ezt most meg kell tanulni!

 

Nyilvánvalóan ezen problémákat a személyi szám megalkotói is észrevették, ezért alkották meg a validátor matematikai képleteket.

 

3. lépés

A 3. lépésben sajnos nem tudjuk tisztázni az SSS számok belső logikáját. A meghatározás szerint "az SSS az azonos napon születettek megkülönböztetésére való". Az enyém 750, annak azonban rendkívül kicsi a valószínűsége, hogy én lettem volna 1968.04.11-én a 750. magyarországi újszülött. Tehát az SSS garantáltan nem sorszámot jelöl, hanem valamilyen rejtett algoritmust hordoznak magukban. Az bizonyos, hogy van valamilyen nem publikált kapcsolatuk az ellenőrző matematikai képletekkel.

 

4. lépés

A 2 db matematikai képlet implementációja:

 

static boolean ValidatorRegi(int[] tomb, int pozicio){
    boolean ok = true;
    int szamlalo = 1;
    int reszeredmeny = 0;
    int eredmeny = 0;
    for(int i = 0; i < 10; i++){
        reszeredmeny += tomb[i] * szamlalo;
        szamlalo++;
    }
    eredmeny = reszeredmeny % 11;
    System.out.println(eredmeny);
    if(eredmeny != pozicio){
        ok = false;
    }
    return ok;
}

static boolean ValidatorUj(int[] tomb, int pozicio){
    boolean ok = true;
    int szamlalo = 10;
    int reszeredmeny = 0;
    int eredmeny = 0;
    for(int i = 0; i < 10; i++){
        reszeredmeny += tomb[i] * szamlalo;
        szamlalo--;
    }
    eredmeny = reszeredmeny % 11;
    System.out.println(eredmeny);
    if(eredmeny != pozicio){
        ok = false;
    }
    return ok;
}

 

5. lépés - Kiértékelés

Érdekes, eddig még nem alkalmazott programozás-technikai megoldás, hogy egy függvény bemeneti paramétereként egy további függvényt adunk meg (static void kiertekeles(boolean obj)). Ilyen esetekben is vigyáznunk kell a típusegyezésekre!

 

static void kiertekeles(boolean obj){
boolean ok = true;
ok = obj;
    if(ok){
        System.out.println("ÉRVÉNYES.");
    }
    else
        System.out.println("NEM ÉRVÉNYES.");
}

 

Mivel azonban a specifikáció hiányos, a kiértékelés inkább a wikipédiás állítások ellenőrzését jelenti. Ennek során mindhárom ellenőrző függvényt felpörgetjük és összevetjük végeredményeiket. Először ismételjük meg a wikipédiás állításokat:

 

Ha egy személyi szám érvényességét akarjuk ellenőrizni:

Tehát:

A feltétel megadása lendületből nem teljes, hiszen mi újság a következő feltétellel:

Mivel ezen feltétel leginkább a legelsőként kiosztott személyi szám sorozatokra illik rá, ezért érdemes őket a ValidatorRegi függvénnyel ellenőrizni. Illetve persze megnézzük mindhárommal.

 

6. lépés - Következtetések

No, ez nem lesz teljes...

Először is: személyi számok nem repkednek az Interneten, ezért már adott mennyiségű összegyűjtésük sem volt problémamentes. Tulajdonképpen nekem 10 db-ot sikerült összeszednem. Mindegyikük ÉRVÉNYES volt a saját ellenőrző (isValidSzemelySzam), valamint a ValidatorRegi szerint. Ez azt bizonyítja, hogy az implementációm helyes (illetve a Wikipédián publikált régi ellenőrző képlet is). Ezen időpontig azonban nem sikerült szereznem olyan személyi számot, amelyiket a ValidatorUj függvény érvényesnek értékelt volna. Emlékezzünk vissza, ezek:

 

ha M = 1, 2, 5, 6 és születési dátum 970101 és 991231 között vannak.

 

Kérem a Tisztelt Olvasót, ha ilyen személyi számmal találkozik, akkor futtassa le az alábbi Java-kódot és írja meg tapasztalatait az info@informatika-programozas.hu címre (természetesen a személyi szám nélkül, bár a születési dátum megadása azért előnyös volna). Fáradozását előre is nagyon köszönöm!

 

Utólagos megjegyzések

  1. Egy 1985-ös személyi számot mindhárom validátor függvény érvényesnek minősített.

  2. Egyik kedves internetes olvasóm küldte az alábbi valós személyi szám ellenőrzést:

    • 1-990930-****

    • A saját ellenőrző függvény szerint:

    • Érvényes

    • A régi képlet szerint:

    • NEM ÉRVÉNYES

    • Az új képlet szerint:

    • Érvényes

www.informatika-programozas.hu - Futtatható Java-kód!

 

 

 

 

 

 

 

 

import java.util.Scanner;
public class Main {

static int[] adatBekeresEllenorzes(){
    int[] szemSzamTomb = new int[11];
    String szamString = "";
    boolean rosszAdat = false;
    int szamlalo = 0;

    System.out.println("\nKérem, hogy adja meg a személyi számát

                                számjegyenként (11 db számjegy 0 és 9 között)!");

    for(int i = 0; i < 11; i++){
        rosszAdat = false;
        System.out.println("Kérem, hogy írja be az " + (i+1) + ". számjegyet!");
        do{
            rosszAdat = false;
            Scanner in = new Scanner (System.in);
            szamString = in.nextLine();
            if(szamString.length() > 1){
                rosszAdat = true;
            }
        char karakter = szamString.charAt(0);
        if(karakter != '0'
            && karakter != '1'
            && karakter != '2'
            && karakter != '3'
            && karakter != '4'
            && karakter != '5'
            && karakter != '6'
            && karakter != '7'
            && karakter != '8'
            && karakter != '9'){
            rosszAdat = true;
        }
        if(rosszAdat == true){
            System.out.println("\nHibás hossz vagy karakter(ek)!" +
                                        " Kérem, hogy csak 0 és 9 közötti egész számokat adjon meg!");
        }
    }while(rosszAdat == true);

    szemSzamTomb[szamlalo] = Integer.parseInt(szamString);
    szamlalo++;
    }
    return szemSzamTomb;
}

static boolean isValidSzemelySzam(int[] tomb){
    boolean ok = true;
    ok = isValidPozicio12345678(tomb[0]);
    ok = isValidPozicio01(tomb[3]);
    ok = isValidPozicio123456789(tomb[4]);
    ok = isValidPozicio0123(tomb[5]);
    return ok;
}

static boolean isValidPozicio12345678(int pozicio){
    boolean ok = true;
    if(pozicio < 1 || pozicio > 8){
        ok = false;
    }
    return ok;
}

static boolean isValidPozicio123456789(int pozicio){
    boolean ok = true;
    if(pozicio < 1 || pozicio > 9){
        ok = false;
    }
    return ok;
}

static boolean isValidPozicio01(int pozicio){
    boolean ok = true;
    if(pozicio != 0 && pozicio != 1){
        ok = false;
    }
    return ok;
}

static boolean isValidPozicio0123(int pozicio){
    boolean ok = true;
    if(pozicio != 0 && pozicio != 1 && pozicio != 2 && pozicio != 3){
        ok = false;
    }
    return ok;
}

static boolean ValidatorRegi(int[] tomb, int pozicio){
    boolean ok = true;
    int szamlalo = 1;
    int reszeredmeny = 0;
    int eredmeny = 0;
    for(int i = 0; i < 10; i++){
        reszeredmeny += tomb[i] * szamlalo;
        szamlalo++;
    }
    eredmeny = reszeredmeny % 11;
    if(eredmeny != pozicio){
        ok = false;
    }
    return ok;
}

static boolean ValidatorUj(int[] tomb, int pozicio){
    boolean ok = true;
    int szamlalo = 10;
    int reszeredmeny = 0;
    int eredmeny = 0;
    for(int i = 0; i < 10; i++){
        reszeredmeny += tomb[i] * szamlalo;
        szamlalo--;
    }
    eredmeny = reszeredmeny % 11;
    if(eredmeny != pozicio){
        ok = false;
    }
    return ok;
}

static void kiiras(int[] tomb){
    for(int i = 0; i < tomb.length; i++){
        System.out.print(tomb[i] + " ");
    }
}

static void kiertekeles(boolean obj){
    boolean ok = true;
    ok = obj;
    if(ok){
        System.out.println("ÉRVÉNYES.");
    }
    else
        System.out.println("NEM ÉRVÉNYES.");
    }

public static void main(String[] args) {
    int[] szemSzamTomb = new int[10];

    szemSzamTomb = adatBekeresEllenorzes();
    System.out.println();

    System.out.println("Az Ön által megadott személyi szám:");
    kiiras(szemSzamTomb);
    System.out.println();

    System.out.print("\nA saját ellenőrző függvény szerint: ");
    kiertekeles(isValidSzemelySzam(szemSzamTomb));

    System.out.print("A régi képlet szerint: ");
    kiertekeles(ValidatorRegi(szemSzamTomb, szemSzamTomb[10]));

    System.out.print("Az új képlet szerint: ");
    kiertekeles(ValidatorUj(szemSzamTomb, szemSzamTomb[10]));
    }
}


Végeredmény:

Az Ön által megadott személyi szám:
x x x x x x x x x x x

A saját ellenőrző függvény szerint: ÉRVÉNYES.
A régi képlet szerint: ÉRVÉNYES.
Az új képlet szerint: NEM ÉRVÉNYES.