Implementácia abstraktných údajových typov Už sme sa venovali abstraktný údajový typ postupnosť, ktorý patrí k základným v aplikatívnom programovaní. Na programovanie zložitých úloh však treba aj iné údajové typy ako napr. množinu, graf, tabuľku. Na ilustráciu v tejto kapitole opíšeme imlementáciu abstraktného údajového typu konečná množina. Na konštrukciu prototypovej implementácie konečných množín použijeme postupnosť. Abstraktný údajový typ sa zvyčajne opisuje signatúrou a axiomatickou špecifikáciou. Tento opis nezávisí od konkrétneho prostriedku, ktorý sa použije na implementáciu údajového typu. Prototyp predstavuje skutočne vytvorený model údajového typu. Často sa nazýva aj prvou implementáciou. Prototyp vlastne preukazuje, že sa špecifikáciou nepožadovalo niečo nemožné. Keďže prototyp by mal iba ukázať vhodnosť implementácia špecifikácie nemusí byť efektívna a výkonná. Dôležitejšie je, aby prototyp bol jednoduchý, aby nám umožnil ukázať, že všetky navrhnuté operácie sú implementované správne. Abstraktný údajový typ množina V tejto časti uvedieme špecifikáciu údajového typu konečná množina, ktorá bude slúžiť ako východisko pri jej implementácii. Aký je rozdiel medzi konečnými množinami a postupnosťami? Pri množinách poradie a viacnásobný výskyt prvkov nie je dôležitý. Napr. tieto množiny považujeme za rovnaké: {7,2,14} {2,14,7} {2,2,14,7} {14,2,2,7,7,2}.Na druhej strane pri postupnostiach je viacnásobný výskyt prvku a poradie prvkov významné a teda tieto postupnosti sú rôzne [7,2,14] [2,14,7] [2,2,14,7] [14,2,2,7,7,2]. Konštruktory Množiny sa 'podobajú' na postupnosti v tom zmysle, že ich môžeme skonštruovať induktívne - n+1 prvkovú množinu môžeme vytvoriť pridaním jedného prvku k n-prvkovej množine. Táto operácia spolu s operáciou vytvorenia prázdnej množiny dovoľuje konštrukciu ľubovoľnej konečnej množiny. Potrebujeme dva konštruktory:
Rozlišovače Ako rozlišovač spojený s prázdnou množinou budeme uvažovať operáciu isempty(S) = true práve vtedy, ak S je prázdna množina.Na testovanie neprázdnej množiny nepotrebujeme špeciálny rozlišovač, môžeme použiť not(isempty(S) ). Rozlišovač isempty môžeme opísať presnejšie pre všetky typy množiny:
isempty(empty) = true isempty(insert(x,S)) = falseTieto rovnosti môžeme interpretovať ako prepisovacie pravidlá, ktoré nám umožňujú vypočítať výsledok vyhodnotenia funkcie isempty pre ľubovoľný povolený vstup:
isempty(empty) => true isempty(insert(x,S)) => false Selektory Keďže prázdna množina nemá žiadne prvky, nemá ani žiadne selektory. Preto sa budeme zaoberať selektormi pre neprázdne množiny. Vzhľadom na to, že pri množinách nezáleží na poradí prvkov nemôžeme uvažovať o operácii insert(7, {2,14}) = {7,2,14} = {14,2,7} = {2,14,7} = ...Existuje teda veľa spôsobov ako vytvoriť nejakú množinu: insert(7, {2,14}) = insert(2, {7,14}) = insert(14, {7,2})Hovoríme, že máme viacnásobné abstraktné reprezentácie množiny. Napr. insert(7, {2,14}) a insert(2, {7,14}) sú dve abstraktné
reprezentácie množiny {7,2,14} . Pre množinu na rozdiel od postupnosti existuje veľa dvojíc prvok/množina, z ktorých sa operáciou insert vytvorí daná množina. Preto pre množiny nemáme žiadny selektor.
Máme nepríjemnú situáciu: údajovú štruktúru, do ktorej môžeme vložiť objekty (pomocou konštruktora), ale z ktorej nemôžeme dostať nič (keďže nemáme žiadne selektory). Z tejto situácie nám môže pomôcť operátor o------------------o----------------------o--------------------o | druh množiny | prázdna | neprázdna | o------------------+----------------------+--------------------o | rozlišovač | isempty | not(isempty) | | konštruktor | empty | insert | | interogátor | - | isin | o------------------o----------------------o--------------------oĎalej vyjadríme formálne požiadavky na operáciu isin. Aby sme mohli charakterizovať správanie operátora isin treba uvažovať jeho aplikáciu na dva druhy množín: prázdnu množinou a neprázdnu množi- nu: isin(x, empty) = false isin(x, insert(y,S)) = (x = y \/ isin(x,S)) Keďže každá konečná množina sa dá vyjadriť ako konečný počet aplikácií operácií Konečnosť množín Doteraz špecifikované operátory a ich vlastnosti sú ešte neúplné. Nevyjadrili sme ešte konečnosť množín a tým sme pripustili aj nekonečné množiny. Jeden spôsob ako vyjadriť konečnosť množín je špecifikovať kardinalitu (počet prvkov) funkcioucard , ktorá musí byť definovaná pre všetky prvky údajového typu:
card: finset tau -> N (N množina konečných prirodzených čísel) card je totálna funkcia na finset tauFunkciu card môžeme špecifikovať vzhľadom na jej správanie pre rôzne druhy množín takto:
card(empty) = 0 card(insert(x,S)) = card(S), ak isin(x,S) card(insert(x,S)) = 1 + card(S) ak not(isin(x,S)) Axióma extenzionality Zdá sa, že okrem mien nie je veľa rozdielov medzi funkciami cons(x,cons(y,S)) <> cons(y,cons(x,S)), pre x<>y cons(x,S) <> cons(x,cons(x,S)) insert(x, insert(y, S)) = insert(y, insert(x, S)) insert(x, S) = insert(x, insert(x, S))Z doteraz uvedenej špecifikácie nemôžeme ukázať, že konečné množiny sa riadia týmito rovnosťami. Potom je naša špecifikácia neúplná, lebo nám nedovoľuje dokázať vlastnosti, ktoré od konečných množín očakávame. Jedným riešením je jednoducho pridať vyššie uvedené rovnosti do špecifikácie. Stále by sme si mohli však byť neistí, či sme pokryli všetky prípady. Lepší prístup je uvažovať o rovnosti množín: dve množiny sú rovnaké práve vtedy, keď majú rovnaké prvky. Túto axiómu extenzionality možno jednoducho vyjadriť takto: S = R práve vtedy ak pre všetky x platí: isin(x,S) = isin(x,R)Axióma extenzionality hovorí, že dve množiny sú rovnaké, ak majú rovnaké prvky (prvky množiny sa označujú pojmom extenzia množiny) nezávisle od toho ako sú zapísané. Jediný spôsob ako zistiť niečo o prvkoch množiny nám umožňuje interogátor isin . Podľa axiómy extenzionality sú dve množiny zameniteľné, ak neexistujú medzi nimi žiadne badateľné rozdiely. Presnejšie, axióma hovorí, že S je zameniteľné s R vo všetkých (referenčne priehľadných) kontextoch, ak výraz isin(x,S) je zameniteľný s výrazom isin(x,R) vo všetkých (referenčne priehľadných) kontextoch.
Axióma extenzionality nám umožňuje dokázať vyššie uvádzané rovnosti. Špecifikácia Do špecifikácie ďalej zaradíme operácie pre prácu s množinami ako sú prienik, zjednotenie, rozdiel, podmnožina. Výhoda vývoja špecifikácie pred prototypom je v tom, že tento postup umožňuje dôkaz vlastností údajového typu predtým ako sa implementuje. SIGNATÚRA údajového typu množina: druhy: tau, finset tau, bool, N operácie: empty: -> finset tau insert: tau x finset tau -> finset tau isempty: finset tau -> bool isin: tau x finset tau -> bool card: finset tau -> N intersect: finset tau x finset tau -> finset tau union: finset tau x finset tau -> finset tau difference: finset tau x finset tau -> finset tau issubset: finset tau x finset tau -> bool =: finset tau x finset tau -> bool SÉMANTIKA EXISTENČNÁ AXIÓMA: empty patri finset tau insert(x, S) patri finset tau, ak x patri tau, S patri finset tau nejake z nepatri finset tau, inak MNOŽINA ROVNOSTÍ: isempty(empty) = true isempty(insert(x,S)) = false isin(x,empty) = false isin(x,insert(y,S)) = (x = y \/ isin(x,S)) card(empty) = 0 card(insert(x,S)) = card S, ak isin(x,S) card(insert(x,S)) = 1 + card S, ak not(isin(x,S)) card je totálna funkcia na finset tau S = R <=> pre všetky x: isin(x, S) = isin(x, R) isin(x, prienik(S,R)) = isin(x,S) /\ isin(x,R) isin(x, union(S,R)) = isin(x,S) \/ isin(x,R) isin(x, difference(S,R)) = isin(x,S) /\ not(isin(x,R)) issubset(empty,S) = true issubset(insert(x,S),R) = isin(x,R) /\ issubset(S,R) Prototyp pre konečné množiny Ako môžeme implementovať konečné množiny? Najskôr treba zvoliť implementujúci údajový typ, a potom naprogramovať operácie. Máme viacero možností: tabuľka, bitový vektor, usporiadané pole, B-strom, postupnosť. Účelom prototypu je, aby jeho implementácia bola jednoduchá. Najlepší výber v tomto prípade je reprezentácia konečnej množiny pomocou postupnosti obsahujúcej jej prvky. Keďže v postupnostiach je poradie a viacnásobný výskyt prvkov významné, a v množinách nie, bude existovať veľa postupností reprezentujúcich tú istú množinu. Napr. množinu {7,2,14} možno reprezentovať postupnosťami: [7,2,14], [2,14,7], [14,7,2], [7,14,2], [2,7,14,2],....Hovoríme, že existuje veľa konkrétnych reprezentácií pre nejakú abstraktnú hodnotu. Porovnajme to s naším predchádzajúcim pozorovaním, že množiny majú tiež viacnásobnú abstraktnú reprezentáciu. Abstraktná hodnota (množina) má viacnásobnú abstraktnú reprezentáciu, lebo viaceré výrazy vytvorené z konštruktorov empty a insert môžu mať tú istú hodnotu. Každá neprázdna postupnosť má jedinečnú abstraktnú reprezentáciu ako kompozíciu aplikácií operácie cons , ale ľubovoľná množina (s najmenej dvoma prvkami) má viacnásobné abstraktné reprezentácie ako kompozície operácií insert .
V skutočnosti však to, že údajový typ má viacnásobné abstraktné reprezentácie je úplne nezávislé od toho, či sa implementuje s viacnásobnými konkrétnymi reprezentáciami. Napr. hoci množiny majú viacnásobné abstraktné reprezentácie, mohli by sme ich implementovať spôsobom, ktorý priraďuje jedinečnú konkrétnu reprezentáciu každej množine (povedzme, napr. usporiadaním prvkov a vylúčením duplikátov). Vzťah medzi viacnásobnými reprezentáciami na abstraktnej a konkrétnej úrovni je znázornený na obrázku nižšie. insert(7, {2,14}) insert(2, {7,14}) insert(14, {2,7}) ... \ | / viacnásobné \ | / abstraktné \ | / reprezentácie \ | / \ | / abstraktná hodnota {7,2,14} / | \ viacnásobné / | \ konkrétne / | \ reprezentácie / | \ / | \ [7,2,14] [14,2,7] [7,2,14,2] .....Mohli by sme vyžadovať, aby postupnosť reprezentujúca množinu bola usporiadaná a bez duplikátov. Sú tu dve ťažkosti. Po prvé to vyžaduje, aby pre bázový typ tau bola definovaná totálna relácia usporiadania (čo nemusí byť). Po druhé, vyžaduje aby operácie ako napr. zjednotenie udržovali poradie, čo komplikuje ich definíciu a prototyp sa stáva ťažko dokázateľným. Na druhej strane cena nejednoznačnej reprezentácie je, že implementácia operácií musí skrývať fakt, že môže existovať veľa rozdielnych postupností, ktoré reprezentujú tú istú abstraktnú hodnotu. Nemôžeme napr. implementovať rovnosť postupností pomocou testu rovnosti pre postupnosti: S = R --> equal(S,R)keďže napr. [7,2] a [7,2,2] sú rozdielne postupnosti, ktoré reprezentujú tú istú množinu.
Používateľovi abstraktného údajového typu množina treba skryť podrobnosti implementácie (ako nejednoznačnú konkrétnu reprezentáciu). Potom možno vykonať zmeny implementácie údajového typu množina bez ovplyvnenia programov, ktoré tento abstraktný typ údajov používajú. Poznamenajme, že insert a union môžu zaviesť do množiny duplikáty, čo je zvolenou reprezentáciou povolené. Väčšina prototypových implementácií operácií sa vytvára podľa takéhoto vzoru: buď sú to jednoduché rekurzívne definície alebo definície v pojmoch už definovaných operácií. PROTOTYP pre údajový typ konečná množina: empty --> nil insert(x, S) --> cons(x, S) isempty(S) --> null(S) isin(x, S) --> member(x, S) card(S) --> 0, ak isempty(S) card(S) --> card(rest(S)), ak not(isempty(S)) /\ isin(first(S),rest(S)) card(S) --> card(rest(S)) + 1 inak intersect(S,R) --> nil, ak isempty(S) intersect(S,R) --> insert(first(S), intersect(rest(S),R))), ak isin(first(S),R) intersect(S,R) --> intersect(rest(S),R)), inak union(S,R) --> append(S,R) difference(S,R) --> nil, ak isempty(S) difference(S,R) --> insert(first(S), difference(rest(S),R)), ak not(isin(first(S),R)) difference(S,R) --> difference(rest(S),R), inak issubset(S,R) --> isempty(difference(S,R)) S = R --> issubset(S,R) /\ issubset(R,S)
|
|||||||||||||||||||||||||||||||||||||||||||||||
|
|