Általánosított Luhn-algoritmus

Használható segédanyagok: Haskell könyvtárainak dokumentációja, (lokális!) Hoogle, a tárgy honlapja és a BE-AD rendszerbe feltöltött beadandók. Ha bármilyen kérdés, észrevétel felmerül, azt a felügyelőknek kell jelezni, nem a diáktársaknak!

A feladatok egymásra épülnek ezért érdemes ezeket a megadásuk sorrendjében megoldani, de legalább megérteni az aktuális feladatot megelőző feladatokat! A függvények definíciójában lehet, sőt javasolt is alkalmazni a korábban definiált függvényeket (függetlenül attól, hogy sikerült-e azokat megadni).

Tekintve, hogy a tesztesetek, bár odafigyelés mellett íródnak, nem fedik le minden esetben a függvény teljes működését, határozottan javasolt még külön próbálgatni a megoldásokat beadás előtt, vagy megkérdezni a felügyelőket!

Részpontszámokat csak az elégséges szint elérése után lehet kapni!

A feladat összefoglaló leírása

A “Luhn mod N” algoritmus a Luhn-algoritmus általánosítása, amely egy egyszerű, ellenőrzőérték kiszámítására alkalmas módszer. Ennek során veszünk egy betűkből sorozatot, majd ezek értékei alapján kiszámítunk egy újabb betűt, amelyet a sorozat végére teszünk. Ezután az így kiegészített betűsorozatot a későbbiekben a módszer fordítottjával tudjuk ellenőrizni, vagyis vesszük az első (n - 1) elemét, kiszámoljuk az ellenőrzőértéket, és megegyezik az utolsó karakter értékével, akkor helyes. A szélesebb körben elterjedt Luhn-algoritmus tízes számrendszerbeli számjegyekkel dolgozik, amelyet például bankkártyaszámok vagy társadalombiztosítási azonosító jelek esetén alkalmaznak a gyakorlatban. A feladatban most ennek azt a változatát kell elkészítenünk, amely egy adott, tetszőleges betűket tartalmazó ábécé alapján képes ellenőrzőkód számolni a sorozatokhoz.

A kódoláshoz egy szótárat vezetünk be. A szótár egy rendezett párokból, mint kulcs-érték párokból, álló lista.

type Entry = (Char, Int)
type Dictionary = [Entry]

(A type kulcsszó segítségével az Entry típus az (Char, Int) típus egy másik neve lesz, illetve a Dictionary típus az Entry típusból alkotott listákat jelenti (ez lesz a szótárunk típusa), hasonlóan a String és [Char] viszonyához. A programban ez semmilyen további megszorítást nem indukál, csupán a beszédesebb függvénytípusok kialakításában segít.)

A szótár megadása (1 pont)

Adjuk meg azt a függvényt, amely előállít egy szótárat a megadott karakterek felhasználásával!

createDictionary :: [Char] -> Dictionary

Test>
[('a', 0), ('b', 1), ('c', 2), ('d', 3), ('e', 4), ('f', 5)] :: Dictionary
Test>
[('0', 0), ('1', 1), ('2', 2), ('3', 3), ('4', 4), ('5', 5), ('6', 6), ('7', 7), ('8', 8), ('9', 9), ('a', 10), ('b', 11), ('c', 12), ('d', 13), ('e', 14), ('f', 15), ('g', 16), ('h', 17), ('i', 18), ('j', 19), ('k', 20), ('l', 21), ('m', 22), ('n', 23), ('o', 24), ('p', 25), ('q', 26), ('r', 27), ('s', 28), ('t', 29), ('u', 30), ('v', 31), ('w', 32), ('x', 33), ('y', 34), ('z', 35)] :: Dictionary
Test>
[('a', 0), ('b', 1), ('c', 2), ('d', 3), ('e', 4), ('f', 5), ('g', 6), ('h', 7), ('i', 8), ('j', 9), ('k', 10), ('l', 11), ('m', 12), ('n', 13), ('o', 14), ('p', 15), ('q', 16), ('r', 17), ('s', 18), ('t', 19), ('u', 20), ('v', 21), ('w', 22), ('x', 23), ('y', 24), ('z', 25)] :: Dictionary

A későbbiekben szükségünk lesz szótárakra, ezért definiálunk három különböző konstans függvényt a createDictionary függvény segítségével.

dictionary1 = createDictionary "abcdef"
dictionary2 = createDictionary ['a'..'z']
dictionary3 = createDictionary (['0'..'9'] ++ ['a'..'z'])

Elem vizsgálata (1 pont)

Adjuk meg azt a függvényt, amely megvizsgálja, hogy egy betű megtalálható-e a megadott szótárban!

isValidCharacter :: Dictionary -> Char -> Bool

Test>
True :: Bool
Test>
False :: Bool
Test>
False :: Bool
Test>
True :: Bool
Test>
True :: Bool

Szöveg vizsgálata (1 pont)

Adjuk meg azt a függvényt, amely megvizsgálja, hogy egy szöveg minden betűje megtalálható-e a megadott szótárban!

isValidCharSequence :: Dictionary -> String -> Bool

Test>
True :: Bool
Test>
False :: Bool
Test>
True :: Bool
Test>
True :: Bool

Kód keresése szótárban (1 pont)

Definiáljuk a findCode függvényt, amely megkeres egy betűhöz tartozó kódot a szótárban! Amennyiben a keresett elem nem található meg a szótárban, használjuk az error függvényt a hiba jelzéséhez!

findCode :: Char -> Dictionary -> Int

Test>
0 :: Int
Test>
5 :: Int
Test>
5 :: Int
Test>
15 :: Int
Test>
⊥₁ :: Int
⊥₁: findCode: illegal character '4'
CallStack (from HasCallStack):
  error, called at ./LuhnModN.lhs:116:18 in main:LuhnModN
Test>
⊥₁ :: Int
⊥₁: findCode: illegal character ' '
CallStack (from HasCallStack):
  error, called at ./LuhnModN.lhs:116:18 in main:LuhnModN

Betűk keresése szótárban (1 pont)

Definiáljuk a findChar függvényt, amely megkeres egy számhoz tartozó betűt a szótárban! Amennyiben a keresett elem nem található meg a szótárban, használjuk az error függvényt a hiba jelzéséhez!

findChar :: Int -> Dictionary -> Char

Test>
'a' :: Char
Test>
'i' :: Char
Test>
'0' :: Char
Test>
⊥₁ :: Char
⊥₁: findChar: illegal code 30
CallStack (from HasCallStack):
  error, called at ./LuhnModN.lhs:136:18 in main:LuhnModN
Test>
⊥₁ :: Char
⊥₁: findChar: illegal code 30
CallStack (from HasCallStack):
  error, called at ./LuhnModN.lhs:136:18 in main:LuhnModN

Szöveg átírása (1 pont)

Adjuk meg azt a függvényt, amely egy szöveget lefordít a szótárban megtalálható kódok listájára!

Megjegyzés: Feltehetjük, hogy a megadott szöveg a szótárban fellelhető betűkből áll.

translate :: Dictionary -> String -> [Int]

Test>
[0, 1, 2, 3, 4, 5] :: [Int]
Test>
[10, 11, 12, 13, 14, 15] :: [Int]
Test>
[21, 10, 11, 13, 10, 0, 0, 7] :: [Int]

Számsorozat transzformálása (1 pont)

Adjuk meg azt a függvényt, amely a lista utolsó elemétől kezdve (jobbról balra haladva) minden második elemet megdupláz!

doubleEverySnd :: [Int] -> [Int]

Test>
[] :: [Int]
Test>
[2] :: [Int]
Test>
[1, 4, 3, 8, 5, 12, 7, 16, 9, 20] :: [Int]
Test>
[2, 2, 6, 4, 10, 6, 14, 8, 18, 10, 22] :: [Int]
Test>
[1, 4, 3, 8, 5, 12, 7, 16, 9, 20, 11, 24, 13, 28, 15, 32, 17, 36, 19, 40, 21, 44, 23, 48, 25, 52, 27, 56, 29, 60, 31, 64, 33, 68, 35, 72, 37, 76, 39, 80, 41, 84] :: [Int]

A könnyebb olvashatóság kedvéért bevezetünk egy szinonimát, amely egy adott számrendszer alapját hivatott jelölni.

type Base = Int

Decimális szám átalakítása (2 pont)

Adjuk meg azt a függvényt, amely egy decimális számot megadott alapú számmá alakít át! A listában a legkisebb helyi érték szerepel legkorábban.

Megjegyzés: A megoldáskor ügyeljünk arra, hogy az alapnak legalább kettőnek kell lennie és a szám nem lehet negatív!

convertToBase :: Int -> Base -> [Int]

Test>
[0] :: [Int]
Test>
[5] :: [Int]
Test>
[4, 1] :: [Int]
Test>
[0, 4] :: [Int]
Test>
⊥₁ :: [Int]
⊥₁: convertToBase: invalid base -10
CallStack (from HasCallStack):
  error, called at ./LuhnModN.lhs:195:32 in main:LuhnModN
Test>
⊥₁ :: [Int]
⊥₁: convertToBase: invalid number -10
CallStack (from HasCallStack):
  error, called at ./LuhnModN.lhs:201:23 in main:LuhnModN

Számlista összegzése (2 pont)

Készítsünk egy olyan függvényt, amely a következő lépésekből áll:

summarize ::  Base -> [Int] -> Int

Test>
35 :: Int
Test>
33 :: Int
Test>
29 :: Int

Ellenőrzőérték előállítása (1 pont)

Állítsuk elő a megadott számsorozathoz tartozó ellenőrzőértéket! Ennek a menete legyen a következő:

getCheckCode :: Base -> [Int] -> Int

Test>
0 :: Int
Test>
1 :: Int
Test>
7 :: Int
Test>
10 :: Int

Ellenőrzőértékhez tartozó betű keresése (1 pont)

Állítsuk elő azt a függvényt, amely egy számsorozat és egy szótár segítségével megadja, hogy a szöveget milyen betűvel kell kiegészíteni!

Megjegyzés: A megoldáshoz használjuk az előző függvényt! Az alapot ebben az esetben a szótár hossza adja!

getCheckChar :: Dictionary -> [Int] -> Char

Test>
'd' :: Char
Test>
'a' :: Char

Szöveg kiegészítése ellenőrzőértékkel (2 pont)

Az előző függvények felhasználásával adjuk meg azt a függvényt, amely egy szótár felhasználásával kiegészíti az adott szöveget a megfelelő ellenőrzőértékkel, amennyiben a szöveg minden eleme megfelelő! Amennyiben a szövegben vannak nem valid értékek, az error függvény segítségével jelezzük ezt!

generate :: Dictionary -> String -> String

Test>
"almafae" :: String
Test>
"20141205a" :: String
Test>
"almafan" :: String
Test>
⊥₁ :: String
⊥₁: generate: illegal sequence "almafa"
CallStack (from HasCallStack):
  error, called at ./LuhnModN.lhs:281:19 in main:LuhnModN
Test>
⊥₁ :: String
⊥₁: generate: illegal sequence "20141205"
CallStack (from HasCallStack):
  error, called at ./LuhnModN.lhs:281:19 in main:LuhnModN

Szöveg validálása (1 pont)

Definiáljuk azt a függvényt, amely leellenőrzi, hogy egy ellenőrzőértékkel ellátott szöveg nem tartalmaz-e hibát! Amennyiben a szövegben vannak nem valid értékek, az error függvény segítségével jelezzük ezt!

validate :: Dictionary -> String -> Bool

Test>
True :: Bool
Test>
True :: Bool
Test>
⊥₁ :: Bool
⊥₁: validate: illegal elements in sequence "almafa"
CallStack (from HasCallStack):
  error, called at ./LuhnModN.lhs:300:33 in main:LuhnModN

Pontozás