Gyakorlati alapok II.
Utóirat: a korrekt római szám validátor
A római számkijelzésben van valamiféle belső, döcögős logika, de természetesen mégsem oly következetes, mint az arab számábrázolás. Éppen ezért olyan nehéz megállapítani belső összefüggéseit, ám most mégis erre kényszerülünk, ha korrekt római szám validátort kívánunk implementálni.
A validálás tehát ebben (mint sok más esetben) azt jelenti, hogy ellenőrizzük a bevitt-bekért karaktersorozatok, tágabb értelemben bármilyen bemeneti adat formai-tartalmi helyességét. Szinte szabványos függvénytípusa boolean, amelynek végső visszatérése értéke (return) true vagy false lehet. A honlapon már sokszor találkozhattunk ilyen jellegű validáló függvénnyel; legtöbbször a boolean rosszAdat = false értékadásból indultunk ki, amelyet a validálás true értékre állíthatott át.
Római számok esetében a következő szempontok alapján kell validálnunk:
-
bevitt karakterek szerint - egyedül a következő karakterek érvényesek: I, V, X, L, C, D, M,
-
digitszám (karakterszám) mennyisége szerint (Digit vagy helyiérték? című fejezet) - ennek minimális értéke 1, maximális értéke 9, mert a legkisebb római szám 1 (ez a I, hiszen a rómaiak nem ismerték a 0-t), a legnagyobb hivatalos római szám pedig 9 karakterből áll, ez a MMMCMXCIX = 3999,
-
digitszám pozíciója szerint - például V nem lehet X előtt,
-
konkrét karakterkombinációk szerint - például rossz a IIII és ennél hosszabb karakterkombináció, egészen 9 digitig, például IIIIIIIII.
-
A konkrét karakterkombinációk szempontja még további alszempontokra bontható, amelyeket az implementáció során külön-külön figyelembe kell vennünk.
A validálás szinte szabványos műveletei a következők lesznek:
-
equals() vagy !equals() - egyenlő, nem egyenlő
-
contains() vagy !contains() - tartalmazza, nem tartalmazza
-
== vagy != - egyenlő, nem egyenlő
-
< vagy > - nagyobb, kisebb
-
és a fentiek kombinációi
A fenti validáló szempontokat nézzük meg szétbontva, pszeudokód formájában is!
Bevitt karakterek szerint
Ha nem I vagy V vagy X vagy L vagy C vagy D vagy M
Digitszám (karakterszám) mennyisége szerint
Ha karakterszám 0 vagy karakterszám < 1 vagy karakterszám > 9
Digitszám pozíciója szerint
-
V nem lehet X előtt
-
I, V nem lehet L előtt
-
I, V, L nem lehet C előtt
-
I, V, X, L nem lehet D előtt
-
I, V, X, L, D nem lehet M előtt
Konkrét karakterkombinációk szerint
Először nézzük meg elméleti síkon:
-
IIII és nagyobb
-
IIV
-
IIIV
-
VV és nagyobb
-
IIX
-
IIIX
-
XXX és nagyobb
-
XXL
-
LL és nagyobb
-
XXC
-
CCCC és nagyobb
-
CCD
-
CCCD
-
DD és nagyobb
-
CCM
-
CCCM
-
MMMM és nagyobb
Mivel itt konkrét, egyúttal statikus karakterkombinációkról beszélhetünk, érdemes őket egyetlen statikus tárolási szerkezetbe, mondjuk tömbbe rendezni. Ekkor a belső logikai struktúrákat is (IIII és nagyobb) konkrét karakterkombinációkká bontjuk ki. Rossz beviteli karakterkombinációk a következők:
-
IIII
-
IIIII
-
IIIIII
-
IIIIIII
-
IIIIIIII
-
IIIIIIIII
-
IIV
-
IIIV
-
VV
-
VVV
-
VVVV
-
VVVVV
-
VVVVVV
-
VVVVVVV
-
VVVVVVVV
-
VVVVVVVVV
-
IIX
-
IIIX
-
XXX
-
XXXX
-
XXXXX
-
XXXXXX
-
XXXXXXX
-
XXXXXXXX
-
XXXXXXXXX
-
XXL
-
LL
-
LLL
-
LLLL
-
LLLLL
-
LLLLLL
-
LLLLLLL
-
LLLLLLLL
-
LLLLLLLLL
-
XXC
-
CCCC
-
CCCCC
-
CCCCCC
-
CCCCCCC
-
CCCCCCCC
-
CCCCCCCCC
-
CCD
-
CCCD
-
DD
-
DDD
-
DDDD
-
DDDDD
-
DDDDDD
-
DDDDDDD
-
DDDDDDDD
-
DDDDDDDDD
-
CCM
-
CCCM
-
MMMM
-
MMMMM
-
MMMMMM
-
MMMMMMM
-
MMMMMMMM
-
MMMMMMMMM
Mivel a római szám validálás láthatóan bonyolult ügy, ezért elképzelhető, hogy a fenti szempontok között lesz redundáns funkcionalitás. (Utólagos megjegyzés: később kiderült, hogy egyértelműen lett.) Ez azonban -ha a fenti szempontok helyesek-, nem lesz probléma, azaz nem fog rossz végeredményt szolgáltatni. Nyilvánvalóan itt az egyetlen nem kívánatos eredmény csakis az lehet, ha a boolean rosszAdat értéke true értékre lesz állítva, miközben ez valójában nem igaz.
Mivel a romaiSzamValidator() függvényt egyetlen metódusként tervezzük, a fenti szempontok jól szegmentáltan, mintegy "almetódusokként" fognak üzemelni. Semmi más dolgunk nem lesz, mint funkciójukat teljesítve meghatározzák, hogy a boolean rosszAdat változó true vagy false állapotban van-e.
A romaiSzamValidator() függvénynek egyetlen, String típusú bemeneti adata lesz (boolean romaiSzamValidator(String romaiString)).
Ugyanakkor megállapíthatunk egyfajta validálási sorrendet is az almetódusok között. Ekkor az általánosabb hibától haladunk az egyre specifikusabb hibákig:
-
ha nem történt karakterbevitel,
-
ha a római szám hossza kisebb 1-nél vagy nagyobb 9-nél,
-
ha rossz karakterbevitel történt,
-
ha rossz karakterkombináció bevitele történt (ez a funkció további, elég bonyolult almetódusokra bontott).
A keletkezett almetódusok a következők:
static boolean romaiSzamValidator(String romaiString){
rosszAdat = false;
rosszAdat = hossz(romaiString);
rosszAdat = karakter(romaiString);
rosszAdat = nemMegengedettString(romaiString);
rosszAdat = nemMegengedettString2(romaiString);
rosszAdat = karakterPozicio(romaiString);
rosszAdat = karakterPozicio2(romaiString);
return rosszAdat;
}
hossz()
A romaiString hosszának ellenőrzése; nem lehet kisebb 1-nél és nagyobb 9-nél. A függvény azt is minősíti, ha nem történt karakterbevitel.
karakter()
Csakis M, D, C, L, X, V, I karakterek kerülhetnek a rendszerbe.
nemMegengedettString()
Nem kerülhetnek a rendszerbe nem megengedett karakterkombinációk, például IIII. Vezérlő metódusa az equals() függvény. A nemMegengedettString() függvényt részben kiválthatja a karakterPozicio2() metódus rutinja, tehát a 2 metódus (de csak részben!) hasonló funkcionalitású. Ám a biztonság kedvéért, illetve azon okból, miszerint a rémbonyolult római számszabályokat a jelen pillanatban képtelen vagyok teljes egészében átlátni-, mindkét metódus önállóan működik.
nemMegengedettString2()
Nem kerülhetnek a rendszerbe további, nem megengedett, de egyre speciálisabb karakterkombinációk, például IIIIVIII. Vezérlő metódusa a contains() függvény.
karakterPozicio()
Karakterpozíciókat minősít az alábbi szabályok alapján:
-
V nem lehet X előtt,
-
I, V nem lehet L előtt,
-
I, V, L nem lehet C előtt,
-
I, V, X, L nem lehet D előtt,
-
I, V, X, L, D nem lehet M előtt.
karakterPozicio2()
További, egyre speciálisabb karakterpozíciós hibák lekezelése:
-
M-hiba: például MIMI, MIIM, egyetlen kivétel MMCM,
-
D-hiba: például DMD,
-
C-hiba: például CXC, CCIC, egyetlen kivétel CCXC,
-
L-hiba: például LIL,
-
X-hiba: XIXI, egyetlen kivétel XIX,
-
XXVX, egyetlen kivétel XXIX,
-
-
V-hiba: például VIV,
-
I-hiba: például IXI, IVII.
Nézzük meg az implementációt, amely önmagában NEM futtatható. Önálló projektbe való illesztéséhez vissza kell lapoznunk a Váltogassunk tovább: római-arab szám váltó I. című fejezet utolsó Java-kódjához.
static String [] nemMegengedettString = new String[58];
static {
nemMegengedettString[0] = "IIII";
nemMegengedettString[1] = "IIIII";
nemMegengedettString[2] = "IIIIII";
nemMegengedettString[3] = "IIIIIII";
nemMegengedettString[4] = "IIIIIIII";
nemMegengedettString[5] = "IIIIIIIII";
nemMegengedettString[6] = "IIV";
nemMegengedettString[7] = "IIIV";
nemMegengedettString[8] = "VV";
nemMegengedettString[9] = "VVV";
nemMegengedettString[10] = "VVVV";
nemMegengedettString[11] = "VVVVV";
nemMegengedettString[12] = "VVVVVV";
nemMegengedettString[13] = "VVVVVVV";
nemMegengedettString[14] = "VVVVVVVV";
nemMegengedettString[15] = "VVVVVVVVV";
nemMegengedettString[16] = "IIX";
nemMegengedettString[17] = "IIIX";
nemMegengedettString[18] = "XXXX";
nemMegengedettString[19] = "XXXXX";
nemMegengedettString[20] = "XXXXXX";
nemMegengedettString[21] = "XXXXXXX";
nemMegengedettString[22] = "XXXXXXXX";
nemMegengedettString[23] = "XXXXXXXXX";
nemMegengedettString[24] = "XXL";
nemMegengedettString[25] = "LL";
nemMegengedettString[26] = "LLL";
nemMegengedettString[27] = "LLLL";
nemMegengedettString[28] = "LLLLL";
nemMegengedettString[29] = "LLLLLL";
nemMegengedettString[30] = "LLLLLLL";
nemMegengedettString[31] = "LLLLLLLL";
nemMegengedettString[32] = "LLLLLLLLL";
nemMegengedettString[33] = "XXC";
nemMegengedettString[34] = "CCCC";
nemMegengedettString[35] = "CCCCC";
nemMegengedettString[36] = "CCCCCC";
nemMegengedettString[37] = "CCCCCCC";
nemMegengedettString[38] = "CCCCCCCC";
nemMegengedettString[39] = "CCCCCCCCC";
nemMegengedettString[40] = "CCD";
nemMegengedettString[41] = "CCCD";
nemMegengedettString[42] = "DD";
nemMegengedettString[43] = "DDD";
nemMegengedettString[44] = "DDDD";
nemMegengedettString[45] = "DDDDD";
nemMegengedettString[46] = "DDDDDD";
nemMegengedettString[47] = "DDDDDDD";
nemMegengedettString[48] = "DDDDDDDD";
nemMegengedettString[49] = "DDDDDDDDD";
nemMegengedettString[50] = "CCM";
nemMegengedettString[51] = "CCCM";
nemMegengedettString[52] = "MMMM";
nemMegengedettString[53] = "MMMMM";
nemMegengedettString[54] = "MMMMMM";
nemMegengedettString[55] = "MMMMMMM";
nemMegengedettString[56] = "MMMMMMMM";
nemMegengedettString[57] = "MMMMMMMMM";
}
static boolean romaiSzamValidator(String romaiString){
rosszAdat = false;
rosszAdat = hossz(romaiString);
rosszAdat = karakter(romaiString);
rosszAdat = nemMegengedettString(romaiString);
rosszAdat = nemMegengedettString2(romaiString);
rosszAdat = karakterPozicio(romaiString);
rosszAdat = karakterPozicio2(romaiString);
return rosszAdat;
}
//Hiba: például nincs bemeneti String
vagy VVVVVVVVVV (10 karakter)
static boolean hossz(String romaiString){
boolean rosszAdat = false;
if((romaiString.length() < 1 || romaiString.length() > 9)){
rosszAdat = true;
}
return rosszAdat;
}
//Hiba: például a
static boolean karakter(String
romaiString){
for(int i = 0; i < romaiString.length(); i++){
karakter = romaiString.charAt(i);
if(karakter != 'I'
&& karakter != 'V'
&& karakter != 'X'
&& karakter != 'L'
&& karakter != 'C'
&& karakter != 'D'
&& karakter != 'M'){
rosszAdat = true;
}
}
return rosszAdat;
}
//Hiba: például IIIIIIIII
static boolean
nemMegengedettString(String romaiString){
for(int i = 0; i < nemMegengedettString.length; i++){
if(nemMegengedettString[i].equals(romaiString)){
rosszAdat = true;
}
else
continue;
}
return rosszAdat;
}
//Hiba: például IIIVVIII
static boolean
nemMegengedettString2(String romaiString){
for(int i = 0; i < romaiString.length(); i++){
for(int j = 0; j <
nemMegengedettString.length; j++){
if(romaiString.length() >=
nemMegengedettString[j].length()){
if(romaiString.contains(nemMegengedettString[j])){
rosszAdat = true;
}
}
else
continue;
}
}
return rosszAdat;
}
//Hiba:
//V nem lehet X előtt
//I, V nem lehet L előtt
//I, V, L nem lehet C előtt
//I, V, X, L nem lehet D előtt
//I, V, X, L, D nem lehet M előtt
static boolean karakterPozicio(String
romaiString){
//A tombIndexOfKarakter tömb
létrehozása, inicializálása
//M=0 D=1 C=2 L=3 X=4 V=5 I=6
for(int i = 0; i <
tombIndexOfKarakter.length; i++){
tombIndexOfKarakter[i] = 0;
}
for(int i = 0; i < romaiString.length(); i++){
karakter = romaiString.charAt(i);
if(karakter == 'M'){
tombIndexOfKarakter[0] = (romaiString.indexOf('M'))
+ 1;
}
else if(karakter == 'D'){
tombIndexOfKarakter[1] = (romaiString.indexOf('D'))
+ 1;
}
else if(karakter == 'C'){
tombIndexOfKarakter[2] = (romaiString.indexOf('C'))
+ 1;
}
else if(karakter == 'L'){
tombIndexOfKarakter[3] = (romaiString.indexOf('L'))
+ 1;
}
else if(karakter == 'X'){
tombIndexOfKarakter[4] = (romaiString.indexOf('X'))
+ 1;
}
else if(karakter == 'V'){
tombIndexOfKarakter[5] = (romaiString.indexOf('V'))
+ 1;
}
else if(karakter == 'I'){
tombIndexOfKarakter[6] = (romaiString.indexOf('I'))
+ 1;
}
}
if(((tombIndexOfKarakter[6] < tombIndexOfKarakter[0]) &&
((tombIndexOfKarakter[6] != 0) && (tombIndexOfKarakter[0] != 0))
|| (tombIndexOfKarakter[5] < tombIndexOfKarakter[0]) &&
((tombIndexOfKarakter[5] != 0) && (tombIndexOfKarakter[0] != 0))
|| (tombIndexOfKarakter[4] < tombIndexOfKarakter[0]) &&
((tombIndexOfKarakter[4] != 0) && (tombIndexOfKarakter[0] != 0))
|| (tombIndexOfKarakter[3] < tombIndexOfKarakter[0]) &&
((tombIndexOfKarakter[3] != 0) && (tombIndexOfKarakter[0] != 0))
|| (tombIndexOfKarakter[2] < tombIndexOfKarakter[0]) &&
((tombIndexOfKarakter[2] != 0) && (tombIndexOfKarakter[0] != 0))
)){
rosszAdat = true;
}
if(((tombIndexOfKarakter[6] < tombIndexOfKarakter[1]) &&
((tombIndexOfKarakter[6] != 0) && (tombIndexOfKarakter[1] != 0))
|| (tombIndexOfKarakter[5] < tombIndexOfKarakter[1]) &&
((tombIndexOfKarakter[5] != 0) && (tombIndexOfKarakter[1] != 0))
|| (tombIndexOfKarakter[4] < tombIndexOfKarakter[1]) &&
((tombIndexOfKarakter[4] != 0) && (tombIndexOfKarakter[1] != 0))
|| (tombIndexOfKarakter[3] < tombIndexOfKarakter[1]) &&
((tombIndexOfKarakter[3] != 0) && (tombIndexOfKarakter[1] != 0))
)){
rosszAdat = true;
}
if(((tombIndexOfKarakter[6] < tombIndexOfKarakter[2]) &&
((tombIndexOfKarakter[6] != 0) && (tombIndexOfKarakter[2] != 0))
|| (tombIndexOfKarakter[5] < tombIndexOfKarakter[2]) &&
((tombIndexOfKarakter[5] != 0) && (tombIndexOfKarakter[2] != 0))
|| (tombIndexOfKarakter[3] < tombIndexOfKarakter[2]) &&
((tombIndexOfKarakter[3] != 0) && (tombIndexOfKarakter[2] != 0))
)){
rosszAdat = true;
}
if(((tombIndexOfKarakter[6] < tombIndexOfKarakter[3]) &&
((tombIndexOfKarakter[6] != 0) && (tombIndexOfKarakter[3] != 0))
|| (tombIndexOfKarakter[5] < tombIndexOfKarakter[3]) &&
((tombIndexOfKarakter[5] != 0) && (tombIndexOfKarakter[3] != 0))
)){
rosszAdat = true;
}
if(((tombIndexOfKarakter[5] < tombIndexOfKarakter[4]) &&
((tombIndexOfKarakter[5] != 0) && (tombIndexOfKarakter[4] != 0))
)){
rosszAdat = true;
}
return rosszAdat;
}
static boolean karakterPozicio2(String romaiString){
//A tombIndexOfKarakter tömb
újrafelhasználása
//M=0 D=1 C=2 L=3 X=4 V=5 I=6
for(int i = 0; i <
tombIndexOfKarakter.length; i++){
tombIndexOfKarakter[i] = 0;
}
for(int i = 0; i < romaiString.length(); i++){
karakter = romaiString.charAt(i);
if(karakter == 'M'){
tombIndexOfKarakter[0] += 1;
}
else if(karakter == 'D'){
tombIndexOfKarakter[1] += 1;
}
else if(karakter == 'C'){
tombIndexOfKarakter[2] += 1;
}
else if(karakter == 'L'){
tombIndexOfKarakter[3] += 1;
}
else if(karakter == 'X'){
tombIndexOfKarakter[4] += 1;
}
else if(karakter == 'V'){
tombIndexOfKarakter[5] += 1;
}
else if(karakter == 'I'){
tombIndexOfKarakter[6] += 1;
}
}
//M-hiba: például MIMI, MIIM, egyetlen
kivétel MMCM
if(tombIndexOfKarakter[0] == 2 && !romaiString.contains("MM")){
rosszAdat = true;
}
else if(romaiString.equals("MMCM") ){
rosszAdat = false;
return rosszAdat;
}
else if(tombIndexOfKarakter[0] == 3 && !romaiString.contains("MMM") ){
rosszAdat = true;
}
//D-hiba: például DMD
else if(tombIndexOfKarakter[1] > 1){
rosszAdat = true;
}
//C-hiba: például CXC, CCIC, egyetlen
kivétel CCXC
else if(tombIndexOfKarakter[2] == 2 && !romaiString.contains("CC")){
rosszAdat = true;
}
else if(romaiString.equals("CCXC") ){
rosszAdat = false;
return rosszAdat;
}
else if(tombIndexOfKarakter[2] == 3 && !romaiString.contains("CCC")){
rosszAdat = true;
}
//L-hiba: például LIL
else if(tombIndexOfKarakter[3] > 1){
rosszAdat = true;
}
//X-hiba: XIXI, egyetlen kivétel XIX
else if(romaiString.equals("XIX") ){
rosszAdat = false;
return rosszAdat;
}
else if(tombIndexOfKarakter[4] == 2 && !romaiString.contains("XX")){
rosszAdat = true;
}
//XXVX, egyetlen kivétel XXIX
else if(romaiString.equals("XXIX") ){
rosszAdat = false;
return rosszAdat;
}
else if(tombIndexOfKarakter[4] == 3 && !romaiString.contains("XXX")){
rosszAdat = true;
}
//V-hiba: például VIV
else if(tombIndexOfKarakter[5] > 1){
rosszAdat = true;
}
//I-hiba: például IXI, IVII
if(tombIndexOfKarakter[6] == 2 && !romaiString.contains("II")){
rosszAdat = true;
}
else if(tombIndexOfKarakter[6] == 3 && !romaiString.contains("III")){
rosszAdat = true;
}
return rosszAdat;
}