A programozás alapjai 1. BMEVIHIAA01 Nagy HF ke szı te si u tmutato Analízis és Specifikáció (Nyelv független) 0. A Házi feladat témája 1. A Házi feladat téma szöveges leírása 2. A feladat résztvevőinek azonosítása 3. A résztvevők közti kapcsolatok elemzése 4. A résztvevők alapvető tulajdonságai 5. Alapvető feladatok 6. A fájlok tartalma 7. Minta Specifikáció 8. Adatszerkezetek leírása Tervezés (C nyelv) 9. Szükséges tulajdonságok és adattípusok összerendelése 10. Adatszerkezetek megvalósítása 11. Alap funkciók ismertetése 12. További funkciók és számítások Implementálás (C nyelv) 13. Adattípusok elkészítése 14. Függvények megírása 15. Adatok mentése/visszatöltése fájlból. 16. A konkrét feladat megoldása függvényként
Nagy HF pe lda Analízis (Nyelv független) 0. A Házi feladat témája Formula-1 1. A Házi feladat téma szöveges leírása Mindenképpen tartalmazza, hogy a programot Ki és elsősorban Mire fogja használni? A Forma-1-es program esetében egyáltalán nem mindegy, hogy a nézők tájékoztatására, a Forma-1- es pilótáknak, vagy éppen a mérnökök számára készítjük a programot. o A példában a nézők tájékoztatására írjuk, a programot elsősorban a kommentátorok használják majd. Tisztázzuk, hogy miből mennyi adatot szeretnénk nyilvántartani. Egy futam vagy több futam eredményei? Időmérő eredmények vagy a verseny eredmények, esetleg mind a kettő? Versenyzők száma? Csapatok kezelése? Kiállások kezelése? Szektoridők kezelése? Egy futam a verseny eredményei több Nem Igen Nem Írjunk fel néhány kérdést, amelynek megválaszolására a programot szeretnénk felkészíteni. Kezdjük a legalapvetőbb kérdésekkel, utána mutassunk összetettebb példákat is. o Ki nyerte a futamot? o Mennyi idővel nyert a nyertes? o Ki futotta a leggyorsabb kört? o Kinél tartott legrövidebb ideig a kerékcsere? o Ki lett az első három helyezett? o Kik estek ki a futamon? o Ki vesztette a legtöbb helyezést a rajtpozíciójához képest? o Ezután válasszunk ki 1-2 bonyolult kérdést, amivel a program képességei szeretnénk megmutatni o Ki volt az a versenyző, aki a x. körben a leggyorsabb kört futotta? o Melyik pilótának volt a legtöbb előzése a futamon?
2. A feladat résztvevőinek azonosítása A feladat szempontjából lényeges résztvevőket kell azonosítani, nem biztos, hogy élőlény, lehet például Tantárgy, Szoba, Nyeremény stb. Ehhez célszerű a feladatban szereplő főneveket elemezni. Ha valamelyik felesleges, vagy pedig hiányzik valami, az később úgyis ki fog derülni. A feladat szövegéből a következő résztvevőket azonosítottam (3-4 résztvevőnél több ne legyen): Verseny Versenyző 3. A résztvevők közti kapcsolatok elemzése Kapcsolat típusa lehet: 1:1, 1:több, több:több i. Verseny Versenyző (a versenyen több versenyző versenyez) 1:több ii. Versenyző (egy versenyzőhöz több időeredmény tartozik) 1:több Ha a résztvevőket jól azonosítjuk, akkor a kapcsolatok feltérképezése is egyszerű feladat. Vesszük az összes lehetséges résztvevőt, és megnézzük, hogy közöttük értelmezhető-e valamilyen kapcsolat, ha igen, akkor felvesszük a listára. Amennyiben tévedünk, akkor a feladathoz feltett kérdéseinkkel próbáljunk rájönni a hibára. Hiba: az időeredmény helyett csak annyit írunk fel, hogy eredmény a résztvevőkhöz. Ekkor a verseny eredmény (1:1) kapcsolat is értelmezhetőnek tűnik, hiszen a versenynek is van eredménye. Ellenben, ha megnézzük, a kérdéseinket, akkor abból világosan látszik, hogy szükségünk van a részeredményekre is. A verseny eredménye pedig a részeredményekből adódik majd össze. Probléma0: Mi az eredmény? Megoldás0: Hát az attól függ, hogy milyen versenyről van szó. A Formula-1-ben a futamon az nyer, aki először halad át a célvonalon, tehát, aki a legrövidebb idő alatt teljesíti a versenyt. A verseny - időeredmény kapcsolat pedig már nem értelmezhető, csak versenyzőnként ezért a versenyző időeredmény kapcsolatot vettük fel. Amennyiben a mindkét irányban értelmezett kapcsolatok közül (Pl: Versenyző 1:T / Versenyző 1:1) az egyik egyértelmű (1:1), akkor ez szintén elhagyható. 4. A résztvevők alapvető tulajdonságai Verseny: Helyszín: szöveg Körök száma: egész szám Versenyző: Rajtszám: egész szám Név: szöveg Rajtpozíció: egész szám : Idő: Idő, ezred másodperc pontossággal Típus: köridő / boksz kiállás ideje
5. Alapvető feladatok Itt kell meggondolni, hogy milyen alapvető feladatokra lesz szükségünk az adatok kezeléséhez, amit résztvevőnként célszerű felsorolni. Verseny adatok felvétele. Versenyző felvétele a versenyhez Versenyző törlése a versenyből (például nem minden versenyző indul végül a futamon) felvétele a versenyzőhöz 6. A fájlok tartalma A verseny és a versenyzők adatait egy szöveges fájlban tároljuk el, míg a versenyzők időeredményeit bináris fájlból olvassuk be. A fájlok a következőképpen néznek ki: Verseny.txt 1. sor: Verseny helyszín: szöveg 2. sor: Körök száma: egész szám 3. sor: Üres sor 4. sortól: Rajtpozíció<TAB>Rajtszám<TAB> Versenyző neve (3 karakter) Monaco 78 17 MSC 22 WEB 38 ROS 44 HAM 510 GRO 65 ALO 76 MAS 89 RAI 918 MAL 101 VET Ido.dat Rajtszám: előjel nélküli egész Idő: tört szám Típus: 1 karakter ( K =köridő, vagy B =boxkiállás ideje) Házi Feladathoz Specifikációt kell készíteni és beadni!!! A Specifikáció az Analízis rész eredményének leírását tartalmazza!
7. Specifikáció Formula-1 A feladat egy Formula-1-es verseny adatait rögzítő program készítése. A program a nézők számára szolgáltat hasznos információkat egy Formula-1-es futamról. A program csak a versenyben szereplő adatokat dolgoz fel, az időmérő edzésen elért időeredményekkel nem foglalkozik. A program a versenyen résztvevő összes versenyző eredményeit képes kezelni. A versenyzők csapataival a program nem foglalkozik. A program képes versenyen elért köridőket és a kiállások idejét is kezelni, a szektoridőket viszont külön nem tartja nyílván. A program az kérdéseket a pilótákhoz rendelt köridők, illetve a bokszkiállások ideje alapján képes megválaszolni. A versenyen több versenyző is indulhat, továbbá minden versenyző összes bokszkiállását, illetve köridejét a program nyilvántartja. A program a kérdés megválaszolásához a szükséges adatokat 2 adatállományban tárolja. Az egyik szöveges és a következő adatokat tartalmazza: Az első sorban a Verseny helyszínét szöveges formátumban, majd a következő sorban a verseny köreinek számát egész számként ábrázolva, a következő sor üres, a további sorokban a versenyen induló versenyzők Rajtpozíciója (egész szám), Rajtszáma (egész szám), valamint a Versenyző 3 betűs kódja szerepel tabulátorral elválasztva egymástól. A másik állomány bináris, melyben az időeredményeket tároljuk. Az állomány minden tagja egy-egy időeredmény bejegyzésnek felel meg, amely a következőket tartalmazza rendre: Rajtszám(egész szám), (törtszám), Típus ( K vagy B karakter) A program konkrét feladata, hogy a következő kérdésre tudjon válaszolni: Ki volt az a versenyző, aki a 23. körben futotta a leggyorsabb körét?
8. Adatszerkezetek leírása Itt a feladatban szereplő résztvevők közti kapcsolatokból érdemes kiindulni. A résztvevők természetes kapcsolatait célszerű az adatszerkezetnek is követnie. A végeredménynek tartalmaznia kell: A feladat részvevőinek darabszámát a feladat specifikációja szerint: o biztosan csak van o több darab, de előre tudható, hogy pontosan mennyi o több darab, de számuk előre nem ismert A résztvevők közt milyen kapcsolat van o 1:1 o 1:több A kapcsolatot milyen egyedi azonosító biztosítja, amennyiben nem találunk ilyet, akkor vegyünk fel valamilyen egyedi azonosítót. Amennyiben egyértelmű a kapcsolat, akkor ez elhagyható. Ellenőrizzük, hogy a specifikációban szereplő fájlok tartalmazzák-e a kapcsolatok biztosításához szükséges azonosítókat! Adatszerkezetek ábrázolása A végeredmény tehát az összefüggő adatokat rendszerezve mutatja be, ami a kérdések megválaszolását segíti. Az egymástól független adatok pedig külön-külön adatszerkezetet alkotnak. A Forma-1 feladat adatszerkezete Résztvevők: Verseny, Versenyző, Darabszámok: Verseny: biztosan egy darab van belőle (a specifikáció szerint) Versenyző: több darab, de számuk előre nem ismert (a specifikáció nem tartalmazta, hogy biztosan 24 versenyző indul a versenyen) : több darab, de számuk előre nem ismert Kapcsolatok: Verseny Versenyző (a versenyen több versenyző versenyez) 1:több Versenyző (egy versenyzőhöz több időeredmény tartozik) 1:több Kapcsolatazonosítók: Verseny Versenyző: nem szükséges, mert biztosan egy darab verseny van, amin a versenyzők indulnak Versenyző : a kapcsolatot a Rajtszám biztosítja (mind a két fájl tartalmazza) Megjegyzés: az időeredményeket típustól függetlenül együtt tároljuk.
Adatszerkezetek ábrázolása: Az előzőek alapján egyetlen összefüggő adatszerkezet jött létre. Látható, hogy az adatszerkezet nem feltétlenül a fájlokban eltárolt adatoknak megfelelő szerkezetet követi, hanem a kérdések megválaszolását segíti az adatok rendszerezésével. Verseny Helyszín: Monaco Körök száma: 78 Versenyző Versenyző Versenyző Versenyző Versenyző Név: HAM Rajtszám: 4 Rajtpozíció: 4 Rajtszám Rajtszám Típus: köridő Idő: 01:11.762 Típus: köridő Idő: 01:12.242 Rajtszám Típus: Boxkiállás Idő: 00:03:335 Miért jó az adatokat rendszerezve tárolni a memóriában? A példánkban bemutatott Formula-1-es futamon a versenyzők száma 24, a köridők várható száma versenyzőnként 78 (amennyiben nem esik ki senki), a boksz kiállások száma versenyzőnként 2-3. Ez azt jelenti, hogy 24 versenyző, és versenyzőnként 81 db időeredményre számítunk, ami összesen 1944 időeredményt jelent. Kérdés: Hányadik körben futotta a leggyorsabb körét Alonso? Megoldás az adatok rendszerezésével: a) Ki kell keresni a pilóták közül Alonso-t (max.: 24 lépés, ha éppen ő az utolsó) b) Ki kell keresni a leggyorsabb köridőt, Alonso idejei közül. (81 lépés) Megoldás rendszerezés nélkül: 1. Ki kell keresni a pilóták közül Alonso-t, hogy megtudjuk a rajtszámát (max.: 24 lépés) 2. Ki kell keresni a leggyorsabb köridőt, az összes idő közül kiválogatva Alonso idejeit (1944 lépés) A rendszerezés előnye: Minden olyan kérdésre, amely egy konkrét versenyzőre vonatkozik megoldható maximum 24+81 = 105 lépésben, egyébként pedig 1968 lépés (majdnem ~20x annyi!!!)
Tervezés (C nyelv) 9. Szükséges tulajdonságok és adattípusok összerendelése A feladat résztvevőihez mindenképpen az adott nyelven leírt (jelenleg a C nyelv) új típust fogunk létrehozni. A résztvevők tulajdonságait, pedig meg kell feleltetni az adott nyelv valamely egyszerű alap típusának, illetve ennek hiányában új egyszerű, vagy összetett típust létrehozni. Specifikációban szereplő típus C nyelv típus Megjegyzés Verseny: Helyszín: szöveg Körök száma: egész szám struct verseny char helyszin[50],vagy char *helyszin unsigned int korok fix, vagy változó maximum 50 karakter Versenyző: struct versenyzo Rajtszám: egész szám unsigned int rajtszam Név: szöveg char nev[4] 3 karakter + \0. Pl: VET \0 Rajtpozíció: egész szám unsigned int rajtpoz : Idő: Idő, ezred másodperc pontossággal Típus: köridő / boksz kiállás ideje char tipus struct idoeredmeny double ido Ehhez nincs beépített típusunk, ezért nekünk kell készíteni, esetleg felhasználhatjuk a double-t. K vagy B karakter
10. Adatszerkezetek megvalósítása Itt lehet az adatszerkezetek konkrét megvalósítását az adott nyelven megtervezni, figyelembe véve a korábbi adatszerkezet leírást. Verseny A feladat specifikációja szerint csak egy verseny eredményeit szükséges kezelnünk, így erre külön adatszerkezetet nem szükséges létrehozni. Versenyzők Azon adataink számára, melyekből többet kell eltárolni, illetve feldolgozni választanunk kell a tároláshoz szükséges egyszerű adatszerkezetek közül. A versenyzők tárolására többféle adatszerkezet is szóba jöhet: a) Tömb b) Egyirányú láncolt lista i. rendezve / rendezetlenül ii. strázsával / strázsák nélkül c) Kétirányú láncolt lista i. rendezve / rendezetlenül ii. strázsával / strázsák nélkül d) Bináris fa i. rendezve / rendezetlenül e) Egyéb A fix méretű tömb nem jó választás, mert a specifikáció szerint nem fix az indulók száma, így ez nem elég rugalmas, ugyanakkor pazarló is lehet. Rendezve, vagy rendezetten tároljam a versenyzőket? A versenyzők rajtszám szerinti rendezése segíti a keresést, az idők beszúrásához. A verseny végeredményét viszont a helyezések sorrendjében lenne jó látni. Tehát több kulcs szerint kéne rendezni, de a második kulcs csak a futam végén lesz meg. A rajtszám szerinti rendezésre bináris fát használva a keresés átlagos lépésszáma log 2 (24) ~ 4,585, rendezés nélkül 12. Ez nem jelent túlzottan nagy különbséget, ezért most az egyszerűbbet választjuk. A könnyebb kezelhetőség miatt viszont a láncolt listát 2 strázsával valósítjuk meg. A helyezések szerinti rendezést pedig majd a futam vége után előállíthatjuk a listát rendezve vagy indexeléssel. Megjegyzés: Amennyiben az egy versenyen indulók száma jelentősen nagyobb lenne, akkor megfontolandó a bonyolultabb, de hatékonyabb adatszerkezet megvalósítása.
ek Az időeredmények tárolása versenyzőnként történik. Az időeredmények körről-körre sorban érkeznek, és ebben a sorrendben célszerű tárolni is őket. Rendezni tehát nem szükséges, ezért egy egyszerű egyirányú láncolt listát nyugodtan használunk, aminek mindig a végére kell majd beszúrnunk, hogy a sorrend megmaradjon. Az egyirányú láncolt lista végére történő beszúráshoz, azonban mindig végig kell menni a lista elemein, ami viszont költséges lehet. A kétirányú láncolt lista alkalmazását viszont semmi nem indokolja igazán. Ezért felveszünk versenyzőnként +1 mutatót a láncolt listához, ami az utolsó érvényes időeredményt mutatja. A beszúrást így mindig a mutatott elem után kell majd elvégezni (a lista végigjárása nélkül), és strázsákra sem lesz szükségünk. A megvalósítandó adatszerkezet A kialakult adatszerkezet tehát a következőképpen néz ki: A verseny egyetlen struktúrával leírható, melyben a verseny adatai mellett a versenyen induló versenyzők láncolt listájára mutató mutatót is elhelyezünk. A versenyzőket egy kétstrázsás rendezetlen egyszeresen láncolt listában tartjuk nyílván, amelyben a versenyzők alap adatai mellett a versenyzők időeredményeit tartalmazó egyszeres láncolt listára való mutatót is elhelyezünk, továbbá a beszúrás könnyítésére, az utolsó időeredményre való mutatót is. Az időeredmények pedig egyszerű strázsa nélküli láncolt listát alkotnak, mely eleje a versenyzőknél kezdődik. Az így kialakult adatszerkezetet nevezzük Fésűs listának. Melyben az egyik láncolt listából ( fésű gerince = versenyzők) egy másik láncolt lista indul ki ( fésű fogai = időeredmények) így kapcsolva össze az egymáshoz tartozó adatokat (a versenyzőket a hozzájuk tartozó időeredményekkel).
Az adatszerkezet grafikusan ábrázolva
11. Alap funkciók ismertetése Ezen funkciók közé tartoznak az adatszerkezet felépítésével, lebontásával, módosításával kapcsolatos funkciók. Új elemet létrehozó függvények Verseny* uj_verseny(char *helyszin, unsigned korok); Versenyzo* uj_versenyzo(char *nev, unsigned rajtszam, unsigned rajtpoz); Idoeredmeny* uj_idoeredmeny(char tipus, double ido); Az adatszerkezet létrehozásához szükséges függvények Versenyzo* uj_versenyzo_lista(); void versenyzo_felvitel(versenyzo* lista, Versenyzo* versenyzo); Versenyzo* versenyzo_keres(versenyzo* lista, unsigned rajtszam); int ido_felvitel(versenyzo* lista, unsigned rajtszam, Idoeredmeny* ido); Az adatok mentésére és visszatöltésére szolgáló függvények int versenyzok_ment(char *fajlnev, Verseny* verseny); int idoeredmenyek_ment(char* fajlnev, Idoeredmeny* lista); Verseny* verseny_beolvas(char* fajlnev); int idoeredmenyek_beolvas(char* fajlnev, Versenyzo* lista); 12. További funkciók és számítások Ilyen funkciók lehetnek keresési és kiválasztási feladatok, melyek a résztvevők közül valamely alap tulajdonság alapján keresnek, választanak ki egyedeket. Ide tartoznak az egyéb számításokhoz használt függvények (távolság számítása pontok között, idő kezelésére óra/perc/másodperc/ezredmásodperc alapon, stb), valamint a teszteléshez, kiíratáshoz szükséges függvények. Példák: Mennyi idő alatt teljesítette a versenyt egy adott pilóta? double verseny_ido(versenyzo* versenyzo); Hányadik körben futotta a leggyorsabb kört egy adott pilóta? unsigned leggyorsabb_kor(versenyzo* versenyzo); Versenyzők rendezése idő szerint void rendezes_ido_szerint(versenyzo* lista); Másodperc alapú idő alapján az órák/percek számítása unsigned ora (double sec); Versenyző kiíratása a képernyőre void kiir_versenyzo(versenyzo* versenyzo); Hányadik körben futották a leggyorsabb körüket a versenyzők? void leggyorsabb_kor_szamitasa(versenyzo* lista); //Hol az eredmény??? Hol tároljuk a számítások eredményeit? Célszerű azokat az eredményeket, amiket minden résztvevőre szükséges kiszámítani a megfelelő résztvevő tulajdonságai között tárolni, így nem szükséges ehhez külön adatszerkezetet létrehozni, csak a már meglévő tulajdonságokat kell bővíteni az eredményekkel.
Implementálás (C nyelv) 13. Adattípusok elkészítése Készítsük el az feladatban szereplő résztvevők típusait, azok alap tulajdonságaival, az adatszerkezethez szükséges kapcsolatokkal, továbbá készítsük fel az eredmények tárolására. A versenyző típus létrehozása typedef struct Versenyzo { unsigned int rajtszam; char nev[4]; unsigned int rajtpoz; Idoeredmeny* start; Idoeredmeny* veg; struct Versenyzo* kov; double verseny_ido; unsigned leggyorsabb_kor; }Versenyzo; 14. Függvények megírása A tervezésnél meghatározott függvényeket kell implementálni. Üres versenyző lista létrehozása Versenyzo* uj_versenyzo_lista() { Versenyzo* eleje; Versenyzo* vege; eleje = uj_versenyzo( ); vege = uj_versenyzo( ); eleje->kov=vege; return eleje; } Idő felvitele egy versenyzőhöz int ido_felvitel(versenyzo* lista, unsigned rajtszam, Idoeredmeny* ido) { /* Elkészíteni */ return 1; }
15. Adatok mentése és visszatöltése fájlokból Az adatok mentésére és visszatöltésére készített függvényeket is tartalmaznia kell a megoldásnak. A feladatokhoz legalább két fájl feldolgozása szükséges, melyből az egyik bináris, a másik, pedig szöveges. A különböző függvények és algoritmusok dokumentálása történhet folyamatábrával, struktogrammal, vagy pszeudokóddal is. Versenyzők beolvasása ek hozzáadása Fájl megnyitása Ha sikerült a fájl megnyitása akkor ciklus amíg(az időeredmények beolvasása sikeres) Keressük ki a most beolvasott rajtszámú versenyzőt. Ha (megtaláltuk a versenyzőt) akkor új időeredmény hozzáadása a versenyzőhöz. egyébként Hiba történt. Fájl bezárása egyébként Hiba a történt.
16. A konkrét feladat megoldása függvényként Az adatszerkezetek fájlból való felépítése, valamint az ehhez szükséges függvények megírása után már csak a konkrét feladat megválaszolása maradt hátra. Ki volt az a versenyző, aki a 23. körben futotta a leggyorsabb körét? A kérdés megválaszolásához végig kell gondolni, hogy egyértelmű-e a kérdésre adható válasz, vagy pedig több lehetséges megoldás is szóba jöhet. A fenti kérdésre nem egyértelmű a válasz, mivel több versenyző is lehet, aki éppen a 23. körben tette meg a leggyorsabb saját körét. Ilyen esetben vagy kiírjuk az összes megoldást, vagy pedig egyértelműsítjük a kérdést, hogy biztosan csak egy helyes megoldás legyen. Ki volt a leggyorsabb versenyző, aki a 23. körben futotta a leggyorsabb körét? A végeredmény (egyszerűsítve, hibakezelések nélkül) int main(void) { Verseny* verseny = verseny_beolvas("verseny.txt"); Versenyzo* akt = verseny->versenyzok->kov; Versenyzo* keresett = NULL; idoeredmenyek_beolvas("ido.dat", verseny->versenyzok); ossz_ido_szamitasa(verseny->versenyzok); leggyorsabb_kor_szamitasa(verseny->versenyzok); rendezes_ido_szerint(verseny->versenyzok); while(akt->kov && keresett==null) { if(akt->leggyorsabb_kor == 23) keresett = akt; akt = akt->kov; } if(keresett!=null) kiir_versenyzo(keresett); else printf("nincs ilyen versenyzo!\n"); } return 0;