Első NZH gyakorlás és konzultáció

Czirkos Zoltán, Kohári Zsolt · 2022.10.16.

Igény szerinti feladatok, vagy minta nagy ZH példák megoldása.

Ez a gyakorlat a próba ZH feladattal együtt az első NZH-ra felkészülésről, gyakorlásról szól. Nem lesz külön az évfolyamnak hirdetett konzultáció, ez most az. Ha vannak kérdéseid az eddigi anyagban tárgyalt feladatokkal kapcsolatban, most tedd fel őket! Ha otthoni gyakorlás során valamelyik − leginkább gyakorlaton nem tárgyalt − feladat megoldásával elakadtál, kérdezz rá.

Ha a gyakorlati anyagban, vagy a példatárban volt olyan feladat, amivel nem boldogultál, javasold, hogy oldjátok meg. A gyakorlatvezető a javaslatokat figyelembe véve választja ki, hogy mi kerül terítékre ezen az órán.

Alább láthatóak a minta nagy ZH feladatai.

1. Próba ZH

A feladat segít felmérni, hogy állsz a tanulással. A szövege csak a többi megoldással együtt jelenik meg. Óra után pedig rögzítsd a pontszámod az admin portálon – a dolgozat nálad marad.

Feladat

Írj függvényt, mely egy valós tömböt kap paraméterként, és visszaad egy mutatót a tömb első negatív elemére. Adjon vissza nullpointert, ha nincs negatív érték.

Írj visszatérési érték nélküli függvényt, amely úgy vesz át két valós változót, hogy képes módosítani az értéküket. Számítsa ki a két érték átlagát, végül mindkét kapott értéket írja felül vele!

Rövid programrészlettel demonstráld a függvények együttes használatát: definiálj egy ötelemű tömböt, keresd meg az első negatív elemét, majd átlagold ki ezt és a legelső elemet!

A feladat megoldására 7 perc áll rendelkezésre.

Mintamegoldás és pontozási útmutató
double *neg(double *tomb, int n) {
    for (int i = 0; i < n; ++i) {
        if (tomb[i] < 0) return tomb+i; // &tomb[i] is jó 
    }
    return NULL;
}

void atlagol(double *a, double *b) {
    double atlag=(*a + *b) / 2;
    *a = *b = atlag; // *a=*b=(*a+*b)/2 is jó
}

double tomb[5] = { 89, 76, 34, -19, 47 };
double *p;
p=neg(tomb, 5);
if (p!=NULL) // akkor sem hagyható el, ha most van negatív elem a példa tömbben
    atlagol(p,&tomb[0]); // atlagol(p,tomb) is jó

Mindegyik tétel 1 pontos:

  1. neg paraméterei: double* és int a tömbhöz, visszatérési típusa double*
  2. tömb bejárása 0..n-1 index (pointerrel ugyanez a tartomány)
  3. keresés: visszaadja a megtalált elem címét
  4. NULL-t ad vissza a ciklus mögött (ha nincs negatív érték)
  5. atlag paraméterei: két double*, más nem lehet, visszatérési típusa void
  6. hasznosítja *a és *b értéket és elvégzi a kért értékadást: *a=... *b=...
  7. Példa: tömb létrehozása (legyen méret megadva vagy inicializálva)
  8. double* változóba teszi a neg függvény által adott pointert, tomb átadásnál nincs &
  9. sikertelen keresés esetén nem hívja az átlagolót
  10. atlagol függvényt a két kért címmel hívja; p-nél nem lehet &
Összkép: ha nincs indentálás, vagy *(tomb+i) alakú kifejezés van: −1p

2. További feladatok

Virágnyelv

BEUGRÓ: Írj függvényt, amelyik igazat ad, ha kisbetűs magánhangzót kapott (a, e, i, o, u)! Használd ezt a következő programodban!

Írj kétparaméterű függvényt, amely a második paraméterként kapott, angol ábécé kisbetűiből álló szöveg virágnyelvű változatát állítja elő az első paramétereként kapott helyen! Ez azt jelenti, hogy a magánhangzókat megkétszerezi, és közéjük egy 'v' betűt rak, pl. „viragnyelv” → „viviravagnyevelv”. Felteheted, hogy az első sztring elég nagy ahhoz, hogy elférjen benne az új szöveg.

Írj főprogramot, melyből meghívod a virágnyelv függvényt, és kiírsz egy feldolgozott szöveget!

Megoldás
#include <stdio.h>

int maganhangzo(char c) {
    return c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u';
}

void virag(char *cel, char *forras) {
    int iforr = 0, icel = 0;
    while (forras[iforr] != '\0') {
        cel[icel++] = forras[iforr];
        if (maganhangzo(forras[iforr])) {
            cel[icel++] = 'v';
            cel[icel++] = forras[iforr];
        }
        iforr++;
    }
    cel[icel] = '\0';
}

int main(void) {
    char eredmeny[50];
    virag(eredmeny, "viragnyelven");
    printf("%s\n", eredmeny);
}

BEUGRÓ: megvan-e a maganhangzo() függvény, helyes-e a fejléce; a virag() függvény használja-e.

Pontozás:

  • maganhangzo(): 1p fejléc, 1p karakter vizsgálata
  • virag(): 1p fejléc, 1p lezáró 0-ig ciklus (ciklusfeltételben strlen() nem ér pontot), 1p betűk másolása, 2p lezáró nulla
  • main(): 1p cél tömb, 1p saját függvény használata, 1p kiírás.

Időpontok

Definiálj típust, amelyben egy időpontot tudsz tárolni, külön óra (0...23), perc (0...59) és másodperc (0...59) értékekkel! Írj függvényt, amelyben paraméterként egy sztringet veszel át, benne egy időponttal! Értelmezd a sztringet, majd add vissza az időpontot az előbb definiált típussal! A sztringben az időpont az alábbi három forma egyikében lesz:

  • 23:17:06 – óra, perc, másodperc, mindegyik két számjeggyel;
  • 15h 09m 53s – itt is óra, perc, másodperc, mindegyik két számjeggyel;
  • 10:15 AM – itt az óra és a perc két-két számjeggyel, a másodperc pedig nincs megadva, 0-nak kell tekinteni. Ez a formátum 12 órás; 12:00 AM = éjfél, 08:00 AM = reggel 8, 12:00 PM = dél, 05:00 PM = a délután 5 órai tea időpontja, azaz 17 óra.

Írj főprogramot, amelyben létrehozol három időpontot tároló változót, és a megírt függvényt használva feltöltöd őket a fenti példaidőpontokkal!

Megoldás

Alább két megoldás is látható, de természetesen elég volt egy megoldást adni. Az első a sscanf() függvényen alapul; az jelzi, hogy hány számot sikerült beolvasni. Ha a formátum stimmel, akkor ez 3 kell legyen. A második megoldás nyersebb, közvetlenül a karakterekkel dolgozik. A lényege ennek is az, hogy valahogyan fel kell ismerni, melyik formátumról van szó. Ha a sztring ötödik karaktere egy kettőspont, akkor csak az első lehet; ha a sztring második karaktere egy h betű, akkor pedig a második.

#include <stdio.h>

typedef struct Idopont {
    int ora, perc, masodperc;
} Idopont;

Idopont megoldas1(char *szoveg) {
    Idopont i;
    char ampm;
    if (sscanf(szoveg, "%d:%d:%d", &i.ora, &i.perc, &i.masodperc) == 3)
        return i;
    if (sscanf(szoveg, "%dh %dm %ds", &i.ora, &i.perc, &i.masodperc) == 3)
        return i;
    sscanf(szoveg, "%d:%d %cM", &i.ora, &i.perc, &ampm);
    i.masodperc = 0;
    i.ora %= 12;
    if (ampm == 'P')
        i.ora += 12;
    return i;
}

Idopont megoldas2_seged(char *ora, char *perc, char *mperc) {
    Idopont i;
    i.ora = (ora[0]-'0') * 10 + (ora[1]-'0');             /* vagy stdlib.h atoi() */
    i.perc = (perc[0]-'0') * 10 + (perc[1]-'0');
    i.masodperc = (mperc[0]-'0') * 10 + (mperc[1]-'0');
    return i;
}

Idopont megoldas2(char *szoveg) {
    Idopont i;
    if (szoveg[5] == ':')
        return megoldas2_seged(szoveg+0, szoveg+3, szoveg+6);
    if (szoveg[2] == 'h')
        return megoldas2_seged(szoveg+0, szoveg+4, szoveg+8);
    i = megoldas2_seged(szoveg, szoveg+3, "00");
    i.ora %= 12;
    if (szoveg[6] == 'P')
        i.ora += 12;
    return i;
}

int main(void) {
    Idopont a = megoldas2("23:17:06");
    Idopont b = megoldas2("15h 09m 53s");
    Idopont c = megoldas2("10:15 AM");
}

A pontozás, az alábbi részfeladatok helyes megoldására:

  • 1 p, struktúra definíciója, struktúra típusú változó létrehozása
  • 1 p, char 'x' és sztring "x", aposztróf/idézőjel helyes használata
  • 2 p, sztring átadása függvénynek, sztringek helyes kezelése (indexelés stb.)
  • 2 p, algoritmus: időpont formátumok megkülönböztetése
  • 3 p, számok kiszedése a sztringekből (bármilyen módszerrel); am/pm kezelése
  • 1 p, főprogram, változók létrehozása, saját függvény helyes használata

Potenciális egyéb pontlevonások:

  • Sztringek helytelen kezelése
  • Lezáró nulla fogalmának nem ismerete, sztring méretének átadása
  • Túlindexelés
  • Kódduplikáció

Továbbá az általános pontozási irányelvek.

Szállóvendégek

Egy hétemeletes szállodában a szobafoglalásokat tömbben tárolják. A szobák a szokásos módon vannak számozva, a százasok adják meg az emeletet, a többi pedig a szoba sorszámát (pl. 712 = 7. emelet, 12. szoba). A földszint a 0. szint, utána 1-től 7-ig az emeletek. Ennél a feladatnál nem kell teljes programot írni, csak a megadott részeket.

  • BEUGRÓ: Definiálj Vendeg nevű típust, amelyik egy szállóvendég adatait (név: max. 50 karakter, szobaszám: egész) tartalmazza! Írj függvényt, amely átvesz egy vendéget, és visszaadja, hogy melyik emeleten lakik!
  • Írj függvényt, amely átvesz egy Vendeg elemekből álló tömböt és egy nevet! Keresse ez meg a névhez tartozó foglalást és adja vissza a megtalált tömbelem címét vagy NULL-t, ha nincs találat!
  • Írj függvényt, amely paraméterként kapja a vendégek tömbjét és egy másik, inicializálatlan tömböt, amelyet a szint sorszámával indexelünk! Írja be az utóbbi tömbbe, hogy az egyes emeleteken hány vendég lakik!
  • Írj függvényt, amely megkapja a vendégek tömbjét, az előző függvénnyel előállítja a betöltöttségek tömbjét, és végül visszatér a legzsúfoltabb emelet sorszámával – tehát azzal, ahol a legtöbb vendég van éppen!
Megoldás
typedef struct Vendeg {
    char nev[50+1];
    int szobaszam;
} Vendeg;

int emelet(Vendeg v) {
    return v.szobaszam / 100;
}

Vendeg *keres(Vendeg *vendegek, int meret, char *nev) {
    for (int i = 0; i < meret; ++i)
        if (strcmp(vendegek[i].nev, nev) == 0)
            return &vendegek[i];
    return NULL;
}

void betoltottseg(Vendeg *vendegek, int meret, int *szintek) {
    for (int i = 0; i <= 7; ++i)
        szintek[i] = 0;
    for (int i = 0; i < meret; ++i)
        szintek[emelet(vendegek[i])] += 1;
}

int legtobb_vendeg(Vendeg *vendegek, int meret) {
    int szintek[8];
    betoltottseg(vendegek, meret, szintek);
    int max = 0;
    for (int i = 1; i < 8; ++i)
        if (szintek[i] > szintek[max])
            max = i;
    return max;
}

Pontozás, beugró értékelési szempontjai:

  • BEUGRÓ: legyen struktúra, tartalmazzon egy sztringet és egy egész számot. (Ha 50-es a karaktertömb, akkor beugrónak elfogadható.) Legyen emelet függvény, amelynek a fejléce legyen rendben.
  • 1 p, struktúra (typedef nem kell). Ha 50 a tömb mérete, nem 51, nem jár a pont.
  • 1 p, emelet(). Simán szobaszám / 100, mert egész osztás van.
  • 2 p, keresés: 1 p algoritmus, 1 p sztring összehasonlítás (strcmp).
  • 3 p, betöltöttség: 1 p fejléc, 1 p nullázás (aminek itt kell megtörténnie!), 1 p hisztogram. A vendégtömb méretét át kell adni, a szinttömb méretét nem, de nem baj, ha ott van. A függvény visszaadhatja az int *szintek-et, de fölösleges. Nem a függvény dolga létrehozni a szintek tömbjét!
  • 3 p, legtöbb vendég: 1 p fejléc és visszatérési érték, 1 p tömb létrehozása + betoltottseg() hívása, 1 p maximum keresése. A maximum keresésében az indexet kell megjegyezni, mert az adja az emelet számát.

Súlyos hibák:

  • Tömbök/pointerek keverése. Pl. nem lehet a betöltöttség függvényben a szintek tömbjének létrehozása.
  • Tömb végén NULL, EOF és hasonló konstansok feltételezése.
  • Túlindexelés.

Továbbá az általános pontozási irányelvek.

Rendezés

BEUGRÓ: Írj függvényt, amely képes megcserélni két paraméterként kapott, valós típusú változó tartalmát!

Írj függvényt, amely paraméterként egy valósakat tartalmazó tömböt vesz át, továbbá két indexet, amelyek egy tartomány elejét és végét határozzák meg! Térjen vissza a függvény a megadott tartományból a legkisebb elem indexével!

Írj függvényt, amely paraméterként egy valós tömböt vesz át, és az előbbi két függvényt is felhasználva növekvő sorba rendezi a tömb számait!

Egészítsd ki ezeket egy főprogrammal, amely létrehoz egy 5 elemű tömböt, rendezi azt, majd kiírja a tömbelemeket sorszámozva!

Megoldás
#include <stdio.h>

void csere(double *x, double *y) {
    double temp = *x;
    *x = *y;
    *y = temp;
}

int minindex(double *tomb, int mettol, int meddig) {
    int min = mettol;
    for (int i = mettol + 1; i <= meddig; ++i)
        if (tomb[i] < tomb[min])
            min = i;
    return min;
}

void rendez(double *tomb, int meret) {
    for (int i = 0; i < meret - 1; ++i) {
        int min = minindex(tomb, i, meret - 1);
        if (min != i)
            csere(&tomb[i], &tomb[min]);
    }
}

int main(void) {
    double tomb[5] = {1.2, 3.5, 9.5, 3.4, 4.5};
    rendez(tomb, 5);
    for (int i = 0; i < 5; ++i)
        printf("%d. %g\n", i, tomb[i]);
}

Beugró: 1) cím szerinti paraméterek legyenek, 2) tudja használni a * operátort a cím szerint átvett változók kiolvasásához, írásához. Ha esetleg void csere(double tomb[], int i1, int i2) van, beugrónak elfogadható.

  • csere(): Két darab double* vagy float* paraméter, és pointerek dereferálása.
  • Lokális segédváltozó valós szám. Kell lennie segédváltozónak, lásd a következőt.
  • Háromlépéses csere. Xor csere nem lehet, mert nem int. *pa += *pb nem lehet, mert 1e40 + 1e-40 == 1e40.
  • minindex(): Fejléc és visszatérési érték helyes. Méret nem kell, mert a két index meghatározza.
  • Minimumkeresés, amely az indexet is képes megtalálni egy ciklusban. Ha értéket keres, aztán újabb ciklus az indexet, ez nem jár. Nem lehet double minval = 0, mert negatív szám is előfordulhat. A mintamegoldás mindkét végén zárt intervallummal dolgozik, de más is elképzelhető.
  • rendez(): Ciklus a paraméterként átvett méretet figyelembe véve.
  • Minimumkeresés függvény hívása.
  • Csere hívása helyes paraméterezéssel, &t[i] vagy t+i.
  • főprogram(): Tömb létrehozása és rendezése. Tömb kezdőcíme és mérete átadva; tomb és &tomb[0] jó, &tomb nem.
  • Tömb kiírása, indexek is látszódjanak.

Továbbá az általános pontozási irányelvek.