Datový typ POLE Vodítkem pro tento kurz Delphi zabývající se pedevším konzolovými aplikacemi a základy programování pro mne byl semestr na vysoké škole. Studenti nyní pipravují semestrální práce pedevším na téma základních poetních operací s maticemi. Rozhodl jsem se, že naše povídání ukoním výkladem o polích, píklad jednorozmrného pole, které bychom mohli pirovnat k vektoru, a také dvourozmrného pole, které je typickým zpracováním maticové algebry, pokud se chceme zabývat v programování tímto problémem. Chtl bych jen zdraznit, že využití polí je veliké a následující píklad by ml studentm pomoci s jejich semestrální prací, být vodítkem a zdrojem i nkterých konkrétních zpracování pro jejich i budoucí práci. Souasn tak ukoníme blok konzolových aplikací, protože programování v Delphi má trochu jiné opodstatnní a konzolová aplikace je pro m osobn pouze zpsob, jak simulovat jazyk Pascal. Jednorozmrné pole - vektor Jestliže si budeme povídat o datovém typu POLE (array), nauíme se souasn definovat vlastní datové typy. Není na tom nic složitého, je teba si jen pamatovat, kam taková definice ve struktue programu patí: Datové typy definujeme ped blokem promnných, což je logické, když takovýto definovaný typ potom chceme použít. Když to vše zobecním, pak bych zaátek kódu programu mohl prezentovat napíklad takto: program MUJ_PROGRAM; {$NJAKÁ_DIREKTIVA} uses NjakýExterníModul; Type TMujTyp = NjakýDefinovanýTyp; Var MojePromnná: TMujTyp;.. Definice procedur a funkcí.. BEGIN.. Hlavní tlo programu.. END. Struktura, kterou jsem navrhl, slouží pouze jako ukázka jednotlivých ástí programu. Nejedná se o funkní program, což je asi z nkterých názv patrné. Nepoužívejte proto tuto ást kódu ve svých programech, zbyten byste se pokoušeli zde nco opravit. Blok TYPE je defininí pro naše typy. Vtšinou se využívá konvence taková, že každý námi definovaný typ oznaujeme v názvu velkým T. V tomto bloku také provedeme definici pole, se kterým budeme v dnešní lekci pracovat. Zaneme tedy vektorem, který v programu budeme definovat jako jednorozmrné pole, nyní již konkrétn: program POLE; {$APPTYPE CONSOLE} uses SysUtils; 1
Type TMojePOle = array[1..10] of integer; Var MPole: TMojePOle; Pro definici pole je klíové slovo array. Aby byla definice promnné typu pole itelnjší, provádí se nejprve definice typu v bloku TYPE, která je odvozena již od definované struktury v Pascalu nebo Delphi. (Bylo by možné promnnou definovat jako: Var xxx: array[1..10] of integer. Definice typ iní kód ovšem itelnjší stejn jako dekompozice hlavního programu na jednotlivé procedury a funkce.) V definici typu íkám, že chci založit jakýsi nový typ, který bude desetiprvkovým polem celoíselných hodnot. Tento typ pak snadno použiji pro definici promnné, se kterou chci pracovat a ešit nkteré úlohy pro vektor. V první chvíli se budeme muset vypoádat s úlohou natení hodnot vektoru, které zadá uživatel z klávesnice. Definujeme si tedy proceduru, jejíž parametrem typ pole, abychom pomocí tohoto podprogramu mohli naítat hodnoty naší promnné MPole: Procedure NactiPOle(Var p: TMojePOle); var i: integer; for i:= 1 to 10 do write('zadejte prvek na pozici '); write(i); readln(p[i]); Zastavme se ješt na chvíli u výkladu kódu. Všimnme si, jakým zpsobem se naítají prvky pole v procedue READLN: Když nebudeme uvažovat použití cyklu, protože je datový typ pole strukturovaný, naítání hodnot pole provádíme jednotliv po dílích prvcích, to znamená v našem pípad zadání deseti ísel na urité pozice, což nám udává promnná i. Kdybych chtl napíklad naíst (nebo zobrazit) tetí prvek pole, uvedl bych v hranatých závorkách konkrétní pozici: p[3], kde p je promnná typu pole. Systém bu naítá nebo vypisuje hodnotu v poli na tetí pozici. K natení všech deseti pozic našeho pole využijeme proto FOR cyklus, protože poet prvk pole je pedem dán. Pro každou hodnotu promnné i tedy provede program jednak njaký výpis, jednak natení hodnoty INTEGER na pozici i v poli typu TMojePole. Nyní proceduru ješt vybavíme ošetením vstupních hodnot: Procedure NactiPOle(Var p: TMojePOle); var i: integer; for i:= 1 to 10 do write('zadejte prvek na pozici '); write(i); {$I-} readln(p[i]); while IOResult <> 0 do 2
write('chyba v zadani! Zadejte znovu prvek na pozici '); write(i); readln(p[i]); {$I+} Ošetení vstupu provedeme pomocí direktivy vstupn výstupních operací a cyklicky pomocí WHILE opakujeme zadání, dokud není hodnota správná. Pak teprve pokraujeme v cyklu FOR natením dalšího prvku pole na urité pozici. Abychom si ukázali jednak zpsob práce s jednotlivými prvky pole znovu a také zkontrolovali správnost natení prvk pole, zaadil jsem do programu jednoduchou proceduru, která provede výpis všech prvk námi zadaného pole. Stojí za povšimnutí opt zpsob výpisu hodnoty vždy na i-té pozici v poli: Procedure Vypis(p: TMOjePOle); var i: integer; for i:= 1 to 10 do writeln(p[i]); Procedura je opt parametrizována typem TMojePole. Výpis se provádí opt v cyklu FOR a probíhá vždy pro hodnotu na i-té pozici. Jak je vidt, není v zápise rozdíl mezi naítáním hodnoty na urité pozici nebo výpisem, takže se tato konvence dobe pamatuje. Pro naši úlohu si pipravíme ješt funkci pro zjištní maxima z hodnot uložených v poli (nebo vektoru), kterou si podrobn popíšeme. Nezapisoval jsem komentáe proto do zdrojového kódu, protože nebude vysvtlení tak triviální. A jak jist pedpokládáte, bude se jednat o parametrizovanou funkci, tentokrát již pouze se vstupními parametry (volanými hodnotou), protože výsledek bude pedávat funkce samotná v podob nejvtšího ísla - maxima. Function NajdiMaximum(p:TMojePOle): integer; Var i: integer; Max: integer; Max:= p[1]; for i:= 2 to 10 do if p[i] > Max then Max:= p[i]; Result:= Max; Jak vyplývá z hlaviky funkce, pracuje tato s typem TMojePole. Výsledná hodnota funkce je celé íslo typu INTEGER. Protože budeme pracovat s polem, definuji si také lokální promnnou i, která mi poslouží v použití cyklu, kterým budeme procházet všechny prvky pole a zjišovat, který z nich by mohl být asi nejvtší. Dále jsem v deklaraci promnných definoval promnnou Max, do které budu prbžn ukládat nejvtší ísla z již srovnaných prvk pole. Jestliže takto projdu všechny prvky, zstane v Max uložena nejvtší hodnota, kterou následn pedáme výstupní hodnot funkce. Podrobn si mžeme projít každý ádek: Nejprve do promnné Max piadíme hodnotu prvního prvku pole. Zatím je tedy tato maximální, protože jsme se k jiným ješt nedostali. Následn pomocí cyklu FOR 3
pistupujeme k dalším (od druhého až po desátý) prvkm a pokud je hodnota na i-té pozici vtší než Max, pak do ní piadíme práv hodnotu pole na pozici i. Pokud takto prozkoumáme všechny prvky pole, zstane nám v promnné Max uložena maximální hodnota z definovaných prvk pole. Nakonec definujeme vlastní tlo programu pomocí pipravených procedur a funkcí a mžeme se tšit na výsledek. program POLE; {$APPTYPE CONSOLE} uses SysUtils; Type TMojePOle = array[1..10] of integer; Var MPole: TMojePOle; Procedure NactiPOle(Var p: TMojePOle); var i: integer; for i:= 1 to 10 do write('zadejte prvek na pozici '); write(i); {$I-} readln(p[i]); while IOResult <> 0 do write('chyba v zadani! Zadejte znovu prvek na pozici '); write(i); readln(p[i]); {$I+} Procedure Vypis(p: TMOjePOle); var i: integer; for i:= 1 to 10 do writeln(p[i]); Function NajdiMaximum(p:TMojePOle): integer; Var i: integer; Max: integer; Max:= p[1]; for i:= 2 to 10 do if p[i] > Max then Max:= p[i]; Result:= Max; BEGIN NactiPOle(MPole); //Nacteni promenne MPole 4
Vypis(MPOle); //Vypis hodnot prom. MPole write('nejvetsi prvek vektoru (MAXIMUM) je: '); writeln(najdimaximum(mpole)); //Pouziti funkce k nalezeni MAX readln; END. Obr. 0-1 Program na zjištní maximální hodnoty ve vektoru o 10 prvcích. Dvourozmrné pole matice Ke zvládnutí poetních úloh s maticemi využijeme poznatk spojených s vektorem. Tyto úlohy nejsou nikterak složité, když si pedstavíme, že je matice také složena z vektor, pak staí, když napíklad pro natení hodnot použijeme namísto jednoho cyklu dva, kdy je jeden do druhého vnoený. Také definice typu matice je pouze o udání další ady složitjší. V našem píklad zkusíme pracovat s maticí o velikosti 5 x 5, což bude pedpokládat zadání celkem 25 hodnot v takovémto poli. Naším cílem bude ze zadaných hodnot v matici vybrat ze všech sloupc maxima a uspoádat je do vektoru. Datový typ pro matici bychom definovali takto: Type TMojeMatice = array[1..5,1..5] of integer; V hranatých závorkách jsou uvedeny celkem dva rozmry pole, takže se jedná o tzv. dvourozmrné pole, které bychom mohli interpretovat práv jako matici. Pak už je definice promnné velmi jednoduchá. Aby náš program mohl správn fungovat, nadefinuji si ješt typ pro vektor, ve kterém maxima uložíme a následn zobrazíme: Type TMojeMatice = array[1..5,1..5] of integer; TMojePOle = array[1..5] of integer; Var MMatice: TMojeMatice; MPole: TMojePole; 5
První ástí ešení naší úlohy bude natení hodnot, které samozejm ošetíme. Použijeme k tomu celkem dva v sob vnoené cykly FOR. Pro tento úel má procedura krom parametru tyu TMojeMatice také lokální promnné i a j, které urují pozici prvku v matici. i ádek j sloupec Procedure NactiMatici(Var m: TMojeMatice); var i,j: integer; for j:= 1 to 5 do write('zadejte prvek na pozici ['); write(i); write(':');write(j); write(']'); {$I-} readln(m[i,j]); while IOResult <> 0 do write('chyba v zadani! Zadejte znovu prvek na pozici '); write(i); write(':');write(j); write(']'); readln(m[i,j]); {$I+} Vlastní obsah procedury není nikterak složitý, když jej budeme umt správn interpretovat. Zastavme se nejprve u cykl FOR: Cyklické naítání dat zahajuje cyklus pro promnnou i, potom je v tomto cyklu vnoeno opakování pro promnnou j a to vždy od 1 do 5. To znamená, že naítané pozice pjdou asi v tomto poadí [i,j]: [1,1], [1,2], [1,3],.., [2,1], [2,2],.. atd. Z toho mžeme usuzovat, že budeme zadávat matici po ádcích, pokud index i pedstavuje ádek matice. (Což je obvyklé.) Vlastní blok cyklu je srozumitelný a velmi se podobá pedchozí úloze pi práci s vektorem. Z povšimnutí stojí zpsob zápisu tení hodnoty na pozici [i,j]. Procedura READLN naítá prvek parametru m na pozici [i,j]. Celkem tak provedeme 25 zadání ísel, piemž systém každé podrobí kontrole správnosti zadání hodnoty. Stejn jako v pípad vektoru si pro pehlednost a možnost kontroly správnosti ešení vypíšeme zadanou matici. Procedura opt využije celkem dvou cykl FOR, které budou do sebe vnoeny, výpis bude proveden stejn jako procedura natení, tedy po ádcích, na konci každého z nich budeme muset tzv. odádkovat a zaít psát další prvky matice v ádku následujícím. 6
Procedure Vypis(m: TMOjeMatice); var i,j: integer; for j:= 1 to 5 do write(m[i,j]); write(' '); První cyklus promnné i má v bloku nejen druhý cyklus, ale také odádkování, takže obsahuje blok end. V druhém cyklu probíhá výpis prvk matice na aktuální pozici [i,j], piemž ízení pozic je stejné jako v pípad natení, takže bude procedura skuten vypisovat ádky matice. Mezi jednotlivými prvky v ádku zapisuji mezery, aby bylo možné ísla v ádku od sebe odlišit. Zbývá zpracovat proceduru, jejíž výstupním parametrem bude vektor maximálních sloupcových hodnot matice, vstupním parametrem bude samozejm typ matice. Na první pohled se to zdá být pomrn komplikovaná úloha, ale ve skutenosti je to jen pochopení procedur a funkcí, parametr procedury a pedevším cykl, kdy zstaneme zase u FOR cyklu s ohledem na konstantní velikost jednak matice, jednak vektoru, se kterými pracujeme jako s promnnými. procedure NajdiMaximum(m:TMojeMatice; Var p:tmojepole); Var i,j: integer; Max: Integer; Max:= m[1,i]; for j:= 2 to 5 do if m[j,i] > Max then Max:= m[j,i]; p[i]:= Max; Cykly jsou do sebe vnoeny stejn jako v ostatních pípadech, tedy opt opakujeme promnnou j v cyklu promnné i. Protože ovšem hledáme sloupcová maxima, musíme pozice prvku, se kterým aktuáln pracujeme, obrátit, tudíž se prvek identifikuje jako m[j,i]. Znamená to de facto, že i pedstavuje sloupec, j potom ádek. Pitom stejn jako v pípad vektoru i zde vždy pro každý sloupec i provádíme natení první hodnoty m[1,i] do promnné Max a pak ji porovnáváme se všemi zbývajícími prvky sloupce v cyklu pro j od 2 do 5. Tím pro jeden prchod prvního cyklu (cyklus promnné i) získáme jednu maximální hodnotu a to ze sloupce matice. Toto se opakuje celkem 5-krát pro každý sloupec matice, tedy pro opakování cyklu promnné i. Kdybych to ekl zjednodušen, tak 5-krát opakujeme cyklus pro promnnou j. Na konci druhého cyklu (cyklus promnné j) pak provádíme zápis do vektoru maxim: p[i]:=max; Zbývá pipravit jen výpis výsledku (Procedura VypisVektor(p: TMojePOle)) a mžeme program vyzkoušet. Zde pedkládám zdrojový kód: 7
program POLE; {$APPTYPE CONSOLE} uses SysUtils; Type TMojeMatice = array[1..5,1..5] of integer; TMojePOle = array[1..5] of integer; Var MMatice: TMojeMatice; MPole: TMojePole; Procedure NactiMatici(Var m: TMojeMatice); var i,j: integer; for j:= 1 to 5 do write('zadejte prvek na pozici ['); write(i); write(':');write(j); write(']'); {$I-} readln(m[i,j]); while IOResult <> 0 do write('chyba v zadani! Zadejte znovu prvek na pozici '); write(i); write(':');write(j); write(']'); readln(m[i,j]); {$I+} Procedure Vypis(m: TMOjeMatice); var i,j: integer; for j:= 1 to 5 do write(m[i,j]); write(' '); procedure NajdiMaximum(m:TMojeMatice; Var p:tmojepole); Var i,j: integer; Max: Integer; Max:= m[1,i]; for j:= 2 to 5 do if m[j,i] > Max then Max:= m[j,i]; p[i]:= Max; 8
Procedure VypisVektor(p: TMojePOle); var i:integer; write(p[i]); write(' '); BEGIN NactiMatici(MMatice); Vypis(MMatice); NajdiMaximum(MMatice, MPOle); writeln('vektor sloupcovych maxim: '); VypisVektor(MPole); readln; END. Správn interpretovat vnoení cykl do sebe je pomrn složité, nejjednodušší zpsob, jak poznáte správnou funkci procedury nebo funkce, kdy používám dva cykly vnoené do sebe je, když si sami pedstavíte sebe jako poíta a zkusíte si indexovat aktuální pozice. Pak nejlépe poznáte, jak by ml program správn fungovat. Až si sami vyzkoušíte nkolik takových úloh zjistíte, že to vlastn není nic složitého, je teba získat jen prvotní pedstavu o tom, co se vlastn spuštním takové struktury dvou cykl bhem ešení této úlohy odehrává. Pomže vám k tomu zdrojový kód a poznámky pod jednotlivými procedurami, které jsem pipravil. Výsledek by mohl vypadat potom teba takto: 9
Obr. 0-2 Vektor sloupcových maxim 10