Tömörítés

Általános tudnivalók

Használható segédanyagok: Haskell könyvtárainak dokumentációja, a tárgy honlapja és a BE-AD rendszerbe feltöltött beadandók. Ha bármi kérdés, észrevétel felmerül, azt a gyakorlatvezetőnek kell jelezni, NEM a mellettünk ülőnek!

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 feladat összefoglaló leírása

A feladatunk az lesz, hogy definiáljuk egy egyszerű tömörítést.

Az általunk megvalósítandó tömörítéshez kódokat fogunk meghatározni. A kódokat az előfordulási gyakoriságuk alapján fogjuk meghatározni úgy, hogy a leggyakrabban előforduló elemhez a legrövidebb, a legritkábbhoz pedig az egyik leghosszabb kód fog tartozni. Természetesen az mindig a szövegtől függ, hogy az egyes elemekhez milyen kódokat rendelünk.

A tömörítéshez szükségünk lesz egy táblára, amely tartalmazza a szöveg elemeit és a hozzájuk tartozó tömörítőkódokat. Ezt a táblát kell majd felépíteni és használni a tömörítéshez.

A tömörítőkódokat tartalmazó tábla reprezentációja a következő:

type CodeTab = [TabEntry]
type TabEntry = (Char, Code)
type Code = [Bit]

data Bit = Zero | One
  deriving (Eq,Show,Data,Typeable)

Például:

Test>
Test>
Test>
Test>
Test>
Test>
Test>
Test>
Test>

A tömörítéshez használt kódok előállítása egy fával szemléltethető, melyet a gyökerétől kezdünk felépíteni, majd a jobb részfát bővítjük folyamatosan addig, amíg minden elemhez nem rendeltünk egy tömörítő kódot. A tömörítéshez fontos lesz, hogy tömörítő kódok meghatározásához a szövegben az elemek előfordulási gyakoriságuk sorrendjében legyenek megadva, azaz legelől a leggyakoribb, stb.

Példa

Nézünk egy egyszerű példát és a példán keresztül a tömörítési lépéseket nagyvonalakban.

Adott az "abrakadabra" szövegünk. Elsőként az ehhez tartozó tömörítőkódokat szeretnénk előállítani, az egyes karakterek előfordulásának a gyakoriságának megállapításával.

A karakterek előfordulási gyakorisága:

'a' -> 5
'b' -> 2
'r' -> 2
'd' -> 1
'k' -> 1

A nekik megfelelő tömörítőkódok:

'a'  -> [Zero]
'b'  -> [One, Zero]
'r'  -> [One, One, Zero]
'd'  -> [One, One, One, Zero]
'k'  -> [One, One, One, One]

Megjegyzés: A kódok az alábbi bináris fa alapján olvashatóak ki.

Compressiona5d3fcdf989d987949f5f47692cabfc5.png

Ha vesszük a fa gyökere és a keresett karakter közötti útvonalat, az élek címkéiről leolvasható az egyes karakterekhez tartozó tömörítőkód.

Miután megállapítottuk a tömörítőkódokat a szövegben előforduló összes karakterhez, a szöveget tömöríteni tudjuk Bit értékek sorozatává.

a -> [Zero]
b -> [One, Zero]
r -> [One, One, Zero]
a -> [Zero]
k -> [One, One, One, One]
a -> [Zero]
d -> [One, One, One, Zero]
a -> [Zero]
b -> [One, Zero]
r -> [One, One, Zero]
a -> [Zero]

Tehát az eredeti szövegünk tömörített változata a következő lesz.

[Zero,One,Zero,One,One,Zero,Zero,One,One,One,One,Zero,One
,One,One,Zero,Zero,One,Zero,One,One,Zero,Zero]

Mivel már tömöríteni tudunk és megalkottuk a megfelelő tömörítési szótárt, ez felhasználható a kitömörítési folyamathoz.

A továbbiakban ezeket a tömörítési és kitömörítési lépéseket nézzük meg részletesebben.

Karakterek gyakoriságának mérése (2 pont)

Adjuk meg azt a függvényt, amely az adott szövegben megadja az egyes karakterek gyakoriságát! A gyakoriságokat a karakterek szerinti lexikografikus sorrendben adjuk meg! Az eredményben minden karakterhez csak egyetlen bejegyzés tartozhat!

getFrequencies :: String -> [(Int, Char)]

Test>
[] :: [(Int, Char)]
Test>
[(5, 'a'), (2, 'b'), (1, 'd'), (1, 'k'), (2, 'r')] :: [(Int, Char)]
Test>
[(2, ' '), (1, '!'), (1, 'S'), (1, 'a'), (1, 'e'), (1, 'l'), (2, 'o'), (1, 'r'), (2, 's'), (2, 'u'), (1, 'v')] :: [(Int, Char)]

Gyakoriság szerinti rendezés (2 pont)

Adjuk meg azt a függvényt, amely rendezi a szövegben található karaktereket azok előfordulásának gyakorisága szerint! Az eredmény szövegében a leggyakrabban előforduló karakter szerepeljen legelől, majd követik csökkenő sorrendben a továbbiak! Azonos gyakorisággal bíró karakterek közt a lexikografikus sorrend döntsön. Az eredményben ne ismétlődjenek karakterek!

orderByFrequency :: String -> [Char]

Test>
[] :: [Char]
Test>
"abrdk" :: [Char]
Test>
" osu!Saelrv" :: [Char]

A tömörítőkódok képzése (1 pont)

Adjuk meg azt a segédfüggvényt, amely megadja a kiosztható kódok közül a következőt az előző kód alapján!

nextPrefixCode :: Code -> Code

Test>
[One, Zero] :: Code
Test>
[One, One, Zero] :: Code

Az összes kód előállítása (1 pont)

Adjuk meg azt a függvényt, mely megadja a kiosztható kódok végtelen sorozatát!

prefixCodes :: [Code]

Test>
[[Zero], [One, Zero], [One, One, Zero]] :: [Code]
Test>
[[Zero], [One, Zero], [One, One, Zero], [One, One, One, Zero], [One, One, One, One, Zero], [One, One, One, One, One, Zero], [One, One, One, One, One, One, Zero], [One, One, One, One, One, One, One, Zero]] :: [Code]

Tömörítőkódok hozzárendelése az egyes karakterekhez (3 pont)

Adjuk meg azt a függvényt, amely egy adott szövegből előállítja az egyes karakterekhez tartozó tömörítőkódokat! Az eredmény legyen egy asszociációs lista (rendezett kulcs-érték párok listája, ahol a karakter a kulcs)!

Fontos:

getTab :: String -> CodeTab

Test>
[('a', [Zero]), ('b', [One, Zero]), ('r', [One, One, Zero]), ('d', [One, One, One, Zero]), ('k', [One, One, One, One])] :: CodeTab
Test>
[(' ', [Zero]), ('o', [One, Zero]), ('s', [One, One, Zero]), ('u', [One, One, One, Zero]), ('!', [One, One, One, One, Zero]), ('S', [One, One, One, One, One, Zero]), ('a', [One, One, One, One, One, One, Zero]), ('e', [One, One, One, One, One, One, One, Zero]), ('l', [One, One, One, One, One, One, One, One, Zero]), ('r', [One, One, One, One, One, One, One, One, One, Zero]), ('v', [One, One, One, One, One, One, One, One, One, One])] :: CodeTab
Test>
[('S', [Zero]), ('O', [One])] :: CodeTab
Test>
[('S', [Zero])] :: CodeTab

Kód megadása egy karakterhez (1 pont)

Definiáljuk a táblában kereső függvényt, amely egy karakter és egy tábla felhasználásával megadja a karakterhez rendelt tömörítő kódot! Feltételezhetjük, hogy a keresett karakterhez mindig tartozik bejegyzés a táblázatban.

lookupCode :: CodeTab -> Char -> Code

Test>
[Zero] :: Code
Test>
[One, One, Zero] :: Code
Test>
[Zero] :: Code

Szöveg tömörítése (2 pont)

Készítsük el a szöveget kóddá alakító függvényt, amely minden egyes karaktert helyettesít a neki megfelelő tömörítőkódot! A függvény a paraméterül kapott szöveg alapján mindig elkészíti a tömörítéshez használt táblát is

encode :: String -> (CodeTab,Code)

Test>
([('a', [Zero]), ('b', [One, Zero]), ('r', [One, One, Zero]), ('d', [One, One, One, Zero]), ('k', [One, One, One, One])], [Zero, One, Zero, One, One, Zero, Zero, One, One, One, One, Zero, One, One, One, Zero, Zero, One, Zero, One, One, Zero, Zero]) :: (CodeTab, Code)
Test>
([('S', [Zero]), ('O', [One])], [Zero, One, Zero]) :: (CodeTab, Code)
Test>
75 :: Int

Az illeszkedő kezdőszelet keresése (1 pont)

Adjuk meg azt a keresőfüggvényt, amely tábla felhasználásával megadja a kódsorozat elejéhez illeszkedő kódú karaktert! Feltételezzük, hogy ez minden esetben megadható!

lookupPrefix :: (CodeTab,Code) -> TabEntry

Test>
('S', [Zero]) :: TabEntry
Test>
('G', [One, One, One, One, One, One, Zero]) :: TabEntry

Szöveg kitömörítése (3 pont)

Készítsük el azt a függvényt, amely egy tömörített kódsorozatból egy tábla segítségével visszafejti az eredeti szöveget.

decode :: (CodeTab,Code) -> String

Test>
"SOS" :: String
Test>
"Nekem kell egy emelkedetebb jegy" :: String
Test>
" " :: String

Pontozás