Kihagyás

7. gyakorlat

4. ZH

A gyakorlat elején 30 percben kerül megírásra a 4. zh. A feladat(ok) témái:

  • szintaktikus hibajavítás, szintaktikailag helytelen, nem fordítható forráskód (függvények) kijavítása a program elvárt viselkedésének megváltoztatása nélkül

Input/Output műveletek FILE-ból/ba

Feladat

Írj egy programot, ami beolvas két egész számot, majd kiírja az összegüket és a szorzatukat!

Lehetséges megoldás
1
2
3
4
5
6
7
8
#include <stdio.h>

int main() {
    int a, b;
    scanf("%d %d", &a, &b);
    printf("Osszeg: %d\nSzorzat: %d\n", a + b, a * b);
    return 0;
}

Feladat

Módosítsuk úgy a programot, hogy az stdin és stdout változókat, valamint az fscanf és fprintf függvényeket használja!

Megjegyzés

Az stdin és stdout az úgynevezett standard bemeneti-kimeneti csatornák. Ezek az azonosítók az stdio.h-ban definiált FILE* típusú változók, és technikailag azonnal használhatók. A printf és scanf ezeket a csatornákat használják, ezeken keresztül kommunikálnak.

Az fscanf és fprintf pont úgy működik mint a scanf és printf, csak éppen tetszőleges fájlokat képesek kezelni (nem fixen a standard csatornákkal dolgoznak). Mindkettőnek van egy plusz paramétere (a paraméterlista elején, a formátumsztring előtt), ahol megadjuk, hogy milyen fájlt használjanak. Ide akár a standard fájlokat is megadhatjuk: az fscanf(stdin, ...) pontosan úgy fog működni, mint a hasonlóképpen felparaméterezett scanf(...), és az fprintf(stdout, ...) is ekvivalens a hasonlóan paraméterezett printf(...) függvényhívással.

Lehetséges megoldás
1
2
3
4
5
6
7
8
#include <stdio.h>

int main() {
    int a, b;
    fscanf(stdin, "%d %d", &a, &b);
    fprintf(stdout, "Osszeg: %d\nSzorzat: %d\n", a + b, a * b);
    return 0;
}

A fájlok adatfolyamként történő kezelése során egy FILE* típusú ún. filemutató azonosítja az állományt. (Ezt a típust az stdio.h headerfile deklarálja.)

1
FILE *nev;

Ahhoz, hogy a háttértáron levő fájl tartalmához hozzáférjünk, a fájlt meg kell nyitnunk. (Ez a "megnyitás" a standard fájlok esetében a program legelején automatikusan megtörténik, ezért lehet őket egyből használni.) A fájl megnyitását a fopen függvény hívásával végezhetjük el, melynek prototípusa:

1
FILE * fopen(const char *filename, const char *mode);

avagy meglévő FILE* esetén:

1
2
FILE* vmi;
vmi = fopen("megnyitandó fájl neve", "megnyitási mód");

A "megnyitandó fájl neve" sztringben annak a fájlnak az elérési útvonalát kell megadnunk, amivel dolgozni szeretnénk (pl.: "be.txt"). A "megnyitási mód" szintén egy sztring, amely a file elérésének módját és a fájl típusát határozza meg (pl.: "rb" vagy "at"). A leggyakoribb elérési módok:

mode sztring elérési mód
"r" Létező fájl megnyitása olvasásra.
"w" Új fájl megnyitása írásra. Ha fájl már létezik, akkor a tartalma elvész.
"a" File megnyitása hozzáírásra. A fájl tartalma megmarad, annak a végéhez tudunk írni. Ha a fájl nem létezett, akkor üresen létrejön.
"r+" Létező fájl megnyitása olvasásra, de akár írhatunk is bele.
"w+" Új fájl megnyitása írásra, de akár olvashatunk is belőle. Ha a fájl már létezik, akkor a tartalma elvész.
"a+" Fájl megnyitása hozzáírásra, de akár olvashatunk is belőle. Ha a fájl nem létezik, akkor üresen létrejön.

Fájltípusok:

mode sztring fájl típusa
"t" Text módú, szöveget változó hosszú sorokban tartalmazó fájl. Ha a megnyitási módban nem adjuk meg a fájl típusát jelző karaktert, akkor az text lesz.
"b" Bináris módú, adatot általában fix hosszúságú egységekben tartalmazó fájl.

Amikor többé nincs szükségünk a megnyitott file(ok)-ra, akkor kell használnunk az fclose() hívást, amely lezárja a fájlt.

1
fclose(vmi);

Feladat

Módosítsuk úgy az előző programot, hogy valódi fájlokat használjon!

Lehetséges megoldás
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#include <stdio.h>

int main() {
    int a, b;
    FILE *infile;                    // beolvasáshoz filemutató
    FILE *outfile;                   // kiíratáshoz filemutató
    infile = fopen("be.txt", "r");   // bementi fájl olvasásra megnyitva
    outfile = fopen("ki.txt", "w");  // kimeneti fájl írásra megnyitva

    fscanf(infile, "%d %d", &a, &b);
    fprintf(outfile, "Osszeg: %d\nSzorzat: %d\n", a + b, a * b);

    fclose(infile);                  // bemeneti fájl lezárása
    fclose(outfile);                 // kimeneti fájl lezárása

    return 0;
}

be.txt

1
3 4

ki.txt

1
2
Osszeg: 7
Szorzat: 12

A be.txt-nek léteznie kell, viszont a ki.txt-t a program magától létrehozza (és ha létezett is előtte, a tartamát felülírja). Ha a megadott állományt nem sikerült megnyitni, vagy ha a FILE struktúrának nem sikerült helyet foglalni a memóriában, NULL lesz a függvényérték.

Feladat

A program kezelje le, ha valamiért nem sikerül megnyitni valamelyik fájlt.

Segítség

Ha a megadott állományt nem sikerült megnyitni, vagy ha a FILE struktúrának nem sikerült helyet foglalni a memóriában, az fopen visszatérési értéke NULL (azaz 0, vagyis hamis) lesz.

Lehetséges megoldás
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>

int main() {
    int a, b;
    FILE *infile;
    FILE *outfile;

    if (!(infile = fopen("be.txt", "r"))) { 
        return 1;
    }
    if (!(outfile = fopen("ki.txt", "w"))) {
        fclose(infile);
        return 1;
    }

    fscanf(infile, "%d %d", &a, &b);
    fprintf(outfile, "Osszeg: %d\nSzorzat: %d\n", a + b, a * b);

    fclose(infile);
    fclose(outfile);
    return 0;
}

Feladat (f0193)

Problémafelvetés:

Írj egy programot, ami bekéri egy torta piskótájának sugarát és magasságát, majd kiszámolja a torta 1cm vastag bevonásához szükséges krém térfogatát 5%-os ráhagyással dolgozva. (A torta alját nem kell bekrémezni, csak az oldalát és a tetejét.)

Algoritmustervezés/Megvalósítás:

A főprogram csak az input/output műveleteket végezze, a számolást külön függvény(ek)ben oldd meg. Az adatok tárolására használj összetett adatszerkezetet, ha van értelme. A program a bemenet.txt nevű fájlból olvassa be az adatokat és a kimenet.txt nevű fájlba írja ki az eredményt.

Ötlet

Vedd elő az 5. gyakorlat f0192-es gyakorló feladatának általad írt megoldását, és azt alakítsd át!

Lehetséges megoldás (m0193)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
/*
 * Szegedi Tudományegyetem
 * Informatikai Tanszékcsoport
 * Szoftverfejlesztés Tanszék
 *
 * Programozás Alapjai
 *
 * Keleti Márton, 2018. őszi félév.
 *
 * Specifikáció:
 *    Input: két lebegőpontos szám, a torta sugara és magassága
 *    Output: 1 cm vastag tortabevonathoz szükséges krém térfogata +5%
 *
 * Algoritmustervezés:
 *    A torta tárolására létrehozunk egy henger struktúrát. A henger
 *    térfogatának és a a körlap területének kiszámítására külön függvényeket
 *    írunk. A bevonat mennyiségének meghatározásához kiszámoljuk a torta
 *    jelenlegi és 1 cm-rel megnövelt térfogatát is. A kettő különbsége plusz 5%
 *    lesz a megoldás.
 *
 * Fordítás:
 *    gcc -Wall -o m0193 m0193.c
 *
 * Futtatás:
 *    ./m0193
 */

#include <stdio.h>
#include <math.h>

typedef struct {
    double sugar;
    double magassag;
} henger;

double korterulet(double sugar) {
    return sugar * sugar * M_PI;
}

double terfogat(henger h) {
    return korterulet(h.sugar) * h.magassag;
}

double tortakrem(henger h) {
    henger uj = {.sugar = h.sugar + 1, .magassag = h.magassag + 1};
    double eredeti = terfogat(h);
    double bevont = terfogat(uj);
    return (bevont - eredeti) * 1.05;
}

int main() {
    henger torta;

    FILE* be = fopen("bemenet.txt", "r");
    if (be == NULL) {
        fprintf(stderr, "Hiányzik a \"bemenet.txt\"!\n");
        return 1;
    }
    fscanf(be, "%lf %lf", &torta.sugar, &torta.magassag);
    fclose(be);

    FILE* ki = fopen("kimenet.txt", "w");
    if (ki == NULL) {
        fprintf(stderr, "A \"kimenet.txt\" nem írható!\n");
        return 2;
    }
    fprintf(ki, "%lf\n", tortakrem(torta));
    fclose(ki);

    return 0;
}
Az alábbi videó egy hasonló (specifikációtól eltérő) megoldást mutat be:

f0193

Feladat (f0201)

Problémafelvetés:

Írj egy programot, ami kiszámolja, majd irányszöggel és nagysággal megadja a hasonlóképpen megadott fizikai erők sorozatának eredőjét a kétdimenziós térben. Az erők sorozatának végét egy 0 nagyságú erő jelzi.

Algoritmustervezés/Megvalósítás:

A főprogram csak az input/output műveleteket végezze, a számolást külön függvény(ek)ben oldd meg. Az adatok tárolására használj összetett adatszerkezetet, ha van értelme. A program a bemenet.txt nevű fájlból olvassa be az adatokat és a kimenet.txt nevű fájlba írja ki az eredményt.

Ötlet

Vedd elő az 5. gyakorlat f0200-as gyakorló feladatának általad írt megoldását, és azt alakítsd át!

Lehetséges megoldás (m0201)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
/*
 * Szegedi Tudományegyetem
 * Informatikai Tanszékcsoport
 * Szoftverfejlesztés Tanszék
 *
 * Programozás Alapjai feladat megoldása
 *
 * Gergely Tamás, 2018. őszi félév.
 *
 * Algoritmustervezés/Megvalósítás:
 *    Kétféle főbb adattípusunk lesz, mindkettő a vektor reprezentálására. Az
 *    egyik a polárkoordináták (irányszög és nagyság, a szög fokokban), a
 *    másik a derékszögű koordinátarendszer (x és y koordináták) szerinti
 *    tárolásra. A konverziót az egyikről a másikra függvényekkel oldjuk meg.
 *    Az összegzéshez az (x,y) verziót használjuk (az egyszerűbb), és külön
 *    függvényt írunk rá.
 *
 * fordítás:
 *    gcc -o m0201 m0201.c -lm
 *
 * futtatás:
 *    ./m0201
 */

#include <math.h>
#include <stdio.h>

typedef struct {
    double x, y;
} vector_xy_t;

typedef struct {
    double irany, nagysag;
} vector_in_t;

vector_xy_t convert_to_xy(vector_in_t v) {
    vector_xy_t retval;
    retval.x = cos(v.irany * M_PI / 180.0) * v.nagysag;
    retval.y = sin(v.irany * M_PI / 180.0) * v.nagysag;
    return retval;
}

vector_in_t convert_to_in(vector_xy_t v) {
    vector_in_t retval;
    if (v.x == 0.0) {
        if (v.y == 0.0) {
            retval.irany = 0.0;
        } else {
            retval.irany = (v.y > 0.0) ? 90.0 : -90.0;
        }
    } else {
        retval.irany = atan(v.y/v.x) * 180.0 / M_PI;
        if (v.x < 0.0) {
            if (v.y < 0.0) {
                retval.irany -= 180.0;
            } else {
                retval.irany += 180.0;
            }
        }
    }
    retval.nagysag = sqrt(v.x*v.x + v.y*v.y);
    return retval;
}

vector_xy_t add(vector_xy_t a, vector_xy_t b) {
    vector_xy_t retval;
    retval.x = a.x + b.x;
    retval.y = a.y + b.y;
    return retval;
}

int main() {
    vector_in_t v_in;
    vector_xy_t v_xy, v_sum = { .x = 0.0, .y = 0.0 };
    FILE *f;
    if ((f = fopen("bemenet.txt", "r")) == NULL) {
        return 1;
    }
    scanf("%lf %lf", &v_in.irany, &v_in.nagysag);
    while (v_in.nagysag != 0.0) {
        v_xy = convert_to_xy(v_in);
        v_sum = add(v_sum, v_xy);
        scanf("%lf %lf", &v_in.irany, &v_in.nagysag);
    }
    fclose(f);
    v_in = convert_to_in(v_sum);
    if ((f = fopen("kimenet.txt", "w")) == NULL) {
        return 1;
    }
    printf("(%lf°, %lf)\n", v_in.irany, v_in.nagysag);
    fclose(f);
    return 0;
}

Rekurzió

Rekurziónak nevezzük, amikor egy függvény önmagát hívja, egy bizonyos feltétel teljesüléséig. Sokkal elegánsabb megoldást kapunk és csökkenti a redundanciát a kódunkban. Használata akkor ajánlott, ha egy bizonyos függvény hívását egymás után többször végre kell hajtani. Vannak kifejezetten olyan problémák, melyek megoldása rekurzióval sokkal könnyebb mint iteratív módon (pl: Hanoi-tornyai probléma). Azonban a számítási idő és a memóriaigény jelentős növekedése miatt az esetek többségében mégis az iteratív megoldás ajánlott.

Feladat (f0126)

Feladat:

Számítsd ki \(n!\) értékét az \(n! = 1 * 2 * ... * (n-1) * n\) képlet segítségével nem rekurzív módon ciklussal, és a \(0! = 1\), \(n! = n * (n-1)!\) képlet segítségével rekurzív módon is! Próbáld ki a megoldásod egyre nagyobb számokra! Előbb-utóbb furcsa eredményeket fogsz kapni. Miért?

Lehetséges megoldás (m0126-1)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/*
 * Szegedi Tudományegyetem
 * Informatikai Tanszékcsoport
 * Szoftverfejlesztés Tanszék
 *
 * Programozás Alapjai
 *
 * Keleti Márton, 2018. őszi félév.
 *
 * Specifikáció:
 *    Input: egy n nem negatív egész szám
 *    Output: n! azaz n faktoriálisa
 *
 * Algoritmustervezés:
 *    Tudjuk, hogy a faktoriális definíciója: 0! = 1, n! = n * (n-1)
 *    Ez egy rekurzív definíció. Definiálunk egy fakt() függvényt, ami 1-nél
 *    kisebb számokra 1-et, minden más esetben n*fakt(n-1)-et ad vissza.
 *
 * Fordítás:
 *    gcc -Wall -o m0126-1 m0126-1.c
 *
 * Futtatás:
 *    ./m0126-1
 */

#include <stdio.h>

long fakt(int n) {
    if (n < 1) {
        return 1;
    } else {
        return n * fakt(n-1);
    }
}

int main() {
    int n;
    scanf("%d", &n);
    printf("%ld\n", fakt(n));
    return 0;
}
Lehetséges megoldás (m0126-2)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
/*
 * Szegedi Tudományegyetem
 * Informatikai Tanszékcsoport
 * Szoftverfejlesztés Tanszék
 *
 * Programozás Alapjai
 *
 * Keleti Márton, 2018. őszi félév.
 *
 * Specifikáció:
 *    Input: egy n nem negatív egész szám
 *    Output: n! azaz n faktoriálisa
 *
 * Algoritmustervezés:
 *    Tudjuk, hogy a faktoriális definíciója: 0! = 1, n! = n * (n-1)
 *    Ez egy rekurzív definíció, azonban ha nem muszáj, kerüljük a rekurzív
 *    függvények használatát, ugyanis lassabbak és több memóriát használnak,
 *    mintha egy ciklust írnánk. Sajnos nem minden rekurzív függvényt lehet
 *    ciklussal helyettesíteni. Jelen esetben viszont ha kifejtjük a képletet,
 *    láthatjuk, hogy szerencsénk van:
 *    n * (n-1)! = n * (n-1) * (n-2)! = ... = n * (n-1) * (n-2) * ... * 2 * 1
 *    Tehát csak össze kell szoroznunk a számokat 1-től n-ig. 
 *
 * Fordítás:
 *    gcc -Wall -o m0126-2 m0126-2.c
 *
 * Futtatás:
 *    ./m0126-2
 */

#include <stdio.h>

long fakt(int n) {
    long eredmeny = 1;
    // 2-től indulunk, mert 1-gyel nincs értelme szorozni
    for (int i = 2; i <= n; ++i) {
        eredmeny *= i;
    }
    return eredmeny;
}

int main() {
    int n;
    scanf("%d", &n);
    printf("%ld\n", fakt(n));
    return 0;
}
Lehetséges megoldás (videó)

Az alábbi videó a fenti kettőhöz hasonló megoldásokat mutat be:

f0126

Sorozatok

Feladat (f0050)

Problémafelvetés:

Egy számtani sorozat első tagja \(A\), differenciája \(D\). Számítsa ki a sorozat \(N\)-dik tagját!

Információ

Egy sorozatot akkor nevezünk számtani sorozatnak, ha a szomszédos elemek különbsége – differenciája – (a sorozatra jellemző) állandó. Jele: \(d\).

Pl. \((a_1=1, d=1) \to 1,2,3,4,\dots\), vagy \((a_1=12, d=15) \to 12,27,42,57,72 \dots\)

Részletesen lásd: Wikipedia

Lehetséges megoldás (m0050)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/*
 * Szegedi Tudományegyetem
 * Informatikai Tanszékcsoport
 * Szoftverfejlesztés Tanszék
 *
 * Programozás Alapjai feladat megoldása
 *
 * Gergely Tamás, 2018. őszi félév.
 *
 * Algoritmustervezés:
 *    A program bekéri az adatokat, és az n. tagra vonatkozó képlet
 *    segítségével kiszámolja az elemet.
 *
 * Megvalósítás:
 *    Mindent a főprogram csinál.
 *
 * fordítás:
 *    gcc -o m0050 m0050.c
 *
 * futtatás:
 *    ./m0050
 */

#include <stdio.h>

int main() {
    double a, d;
    int n;
    printf("Az első tag: ");
    scanf("%lf", &a);
    printf("A differencia: ");
    scanf("%lf", &d);
    printf("Hányadik tagot számoljam? ");
    scanf("%d", &n);
    a += (n - 1) * d;
    printf("A sorozat %d. tagja %lf\n", n, a);
    return 0;
}

Az alábbi videó egy hasonló (specifikációtól eltérő) megoldást mutat be:

f0050

Feladat (f0052)

Problémafelvetés:

Egy mértani sorozat első tagja \(A\), hányadosa \(Q\). Számítsa ki a sorozat \(N\)-dik tagját!

Megvalósítás:

A math.h a tartalmaz egy kétparaméteres hatványozó függvényt, a

1
double pow(double,double)

függvényt, melynek első paramétere az alap, második paramétere a kitevő.

Információ

Egy sorozatot akkor nevezünk mértani sorozatnak, ha (a másodiktól kezdve) bármelyik tag és az azt megelőző tag hányadosa állandó. Ezt a hányadost idegen szóval kvóciensnek nevezzük. Jele: \(q\).

Pl. \((a_1=3, q=3) \to 3, 9, 27, 81, \dots\)

Részletesen lásd: Wikipedia

Lehetséges megoldás (m0052)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/*
 * Szegedi Tudományegyetem
 * Informatikai Tanszékcsoport
 * Szoftverfejlesztés Tanszék
 *
 * Programozás Alapjai feladat megoldása
 *
 * Gergely Tamás, 2018. őszi félév.
 *
 * Algoritmustervezés:
 *    A program bekéri az adatokat, és az n. tagra vonatkozó képlet
 *    segítségével kiszámolja az elemet.
 *
 * Megvalósítás:
 *    Mindent a főprogram csinál. Hatványozásra a pow(double, double)
 *    függvényt használjuk.
 *
 * fordítás:
 *    gcc -o m0052 m0052.c -lm
 *
 * futtatás:
 *    ./m0052
 */

#include <math.h>
#include <stdio.h>

int main() {
    double a, q;
    int n;
    printf("Az első tag: ");
    scanf("%lf", &a);
    printf("A hányados: ");
    scanf("%lf", &q);
    printf("Hányadik tagot számoljam? ");
    scanf("%d", &n);
    a *= pow(q, n - 1);
    printf("A sorozat %d. tagja %lf\n", n, a);
    return 0;
}

Az alábbi videó egy hasonló (specifikációtól eltérő) megoldást mutat be:

f0052

Algoritmus feladatok

Feladat (f0115)

Feladat:

Írj programot, mely bekér egy \(n\) pozitív egész számot, és megadja az első \(n\) természetes szám összegét! Készíts kétféle algoritmust két külön függvényben, és mindkettővel számoltasd ki az eredményt:

  • Az első egy mechanikus, ciklust használó algoritmus legyen.
  • A másik matematikailag átgondolt, minél egyszerűbb algoritmus legyen.

Hasonlítsd össze a két megoldás futásidejét! Ehhez használj long long int típusú értékeket (különben int használatával túlcsordulás nélkül még a hosszabb futásidejű algoritmus is túl gyorsan lefutna), és milliárdos (a mai processzorok GHz-es órajelével összevethető) nagyságrendű inputot. Futásidő mérésére használhatod a linux-os /usr/bin/time parancsot, például:

1
echo 1000 | /usr/bin/time -f %U program

Ez így a program érdemi része által felhasznált CPU időt mutatja (századmásodperc pontossággal). A program inputját azért érdemes ilyen módon megadni, hogy a beolvasás sebessége (a felhasználó nagyságrendekkel lassabb reakciója) minél kevésbé befolyásolja az eredményt. A programból vagy kétféle verziót kell fordítani (amik fixen a két függvény valamelyikét használják), vagy egy plusz inputtal választhatóvá kell tenni, hogy a program melyik függvénnyel dolgozzon.

Lehetséges megoldás (m0115)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
/*
 * Szegedi Tudományegyetem
 * Informatikai Tanszékcsoport
 * Szoftverfejlesztés Tanszék
 *
 * Programozás Alapjai feladat megoldása
 *
 * Gergely Tamás, 2018. őszi félév.
 *
 * Algoritmustervezés:
 *    A program bekér egy karaktert majd egy számot, és a karakter alapján
 *    futtatja a megfelelő algoritmust.
 *
 * Megvalósítás:
 *    Egy programot írunk, benne két függvényt, és long long int típust
 *    használunk. A 'c' a ciklusos, a 'k' a képletes megoldást jelenti.
 *
 * fordítás:
 *    gcc -o m0115 m0115.c -lm
 *
 * futtatás:
 *    echo c 100000000 | /usr/bin/time -f %U ./m0115
 *    echo k 100000000 | /usr/bin/time -f %U ./m0115
 */

#include <stdio.h>

long long int ciklus(long long int n) {
    long long int sum = 0ll, i;
    for (i = 1ll; i <= n; ++i) {
        sum += i;
    }
    return sum;
}

long long int keplet(long long int n) {
    return n * (n + 1ll) / 2ll;
}

int main() {
    char a;
    long long int n, s;
    scanf("%c %lld", &a, &n);
    if (a == 'c') {
        s = ciklus(n);
    } else if (a == 'k') {
        s = keplet(n);
    } else {
        return 1;
    }
    printf("1 + ... + %lld = %lld\n", n, s);
    return 0;
}

Az alábbi videó egy hasonló (specifikációtól eltérő) megoldást mutat be:

f0115

Feladat (f0165)

Problémafelvetés:

Határozd meg egy \(N \times M\)-es véletlen értékű elemekkel feltöltött egész mátrix transzponáltját.

Specifikáció:

Az input két egész szám NxM alakban, ahol \(N\) és \(M\) (mindkettő maximum 128) a mátrix sorainak és oszlopainak száma.

Az output .csv formátumú (pontosvesszővel elválasztott) táblázat. Ebbe a bal felső saroktól kezdve kell kiírni a véletlen mátrixot, majd egy sor kihagyásával az első oszloptól kezdve a transzponáltját. Például 2x3 input esetén:

1
2
3
4
5
6
1;2;3;
4;5;6;
;
1;4;
2;5;
3;6;

Algoritmustervezés/Megvalósítás:

A .csv a Comma Separated Values rövidítése. Ez a formátum tetszőleges számú sort tartalmaz, ahol egy sort vesszővel (vagy tágabb értelemben véve pontosveszővel, kettősponttal vagy tabulátorral) elválasztott elemek sorozata alkot. A sor utolsó eleme után nem kötelező az elválasztó jel.

Segítség

Egy mátrix transzponáltját úgy kapjuk, hogy a mátrixot tükrözzük a főátlójára. Egy \(n\times m\)-es mátrixból egy \(m\times n\)-es mátrix lesz, az eredeti mátrix \((i,j)\) pozíciójú eleme a transzponálás után a \((j,i)\) pozícióba kerül. Pl. az

1
2
1 2 3
4 5 6
mátrixból transzponálás után
1
2
3
1 4
2 5
3 6
lesz.

Részletesen lásd: Wikipedia

Segítség

Egy C programban lehetőségünk van pszeudo-random (véletlenszerűnek tűnő) számok (számsorozat) generálására. Ehhez a program elején szükségünk lesz az alábbi include-okra:

1
2
#include <stdlib.h>
#include <time.h>

Ezután az első véletlenszám generálása előtt (praktikusan valahol a main függvény elején) a véletlenszám-generátort is inicializálni kell:

1
srand(time(NULL));

Végül, ahol egy véletlen számra van szükségünk, ott a rand(); függvényt hazsnálhatjuk, pl.:

1
veletlen = rand();
Ez egy előre megadott intervallumba eső nemnegatív egész számot ad vissza. Ha nekünk kisebb intervallum kell, akkor nézhetjük a kapott érték maradékát: pl. a rand() % 7 kifejezés egy \([0, 6]\) közötti véletlenszámot fog adni.

Videó a randomszám generálásról:

Random szám generálás

Lehetséges megoldás (m0165)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
/*
 * Szegedi Tudományegyetem
 * Informatikai Tanszékcsoport
 * Szoftverfejlesztés Tanszék
 *
 * Programozás Alapjai
 *
 * Keleti Márton, 2018. őszi félév.
 *
 * Specifikáció:
 *    Input: két egész szám "NxM" alakban, N és M <= 128
 *    Output: .csv formátumú (pontosvesszővel elválasztott) táblázat az eredeti
 *            és a transzponált mátrixszal
 *
 * Algoritmustervezés:
 *    Beolvassuk N-t és M-et egy-egy változóba, majd lefoglalunk egy NxM-es
 *    egész számokból álló tömböt. Dupla for ciklussal végigmegyünk az elemein
 *    és a rand() függvény segítségével értéket adunk nekik. Az első kiíratás
 *    ugyanúgy megy, mint a feltöltés. A másodiknál viszont transzponálnunk
 *    kell, ehhez az indexelést felcseréljük (azaz nem sorfolytonosan, hanem
 *    oszlopfolytonosan járjuk be a tömböt). Vigyázzunk, nehogy túlindexeljük
 *    a tömböt!
 *
 * Fordítás:
 *    gcc -Wall -o m0165 m0165.c
 *
 * Futtatás:
 *    ./m0165
 */

#include <stdio.h>
#include <stdlib.h>  // rand() és srand() függvények miatt
#include <time.h>    // time() függvény miatt

int main() {
    // véletlenszámgenerátor inicializálása az aktuális időre,
    // így minden futásnál más eredményt fogunk kapni
    srand(time(NULL));

    int n, m;
    scanf("%dx%d", &n, &m);

    int tomb[n][m];
    for (int i = 0; i < n; ++i) {
        for (int j = 0; j < m; ++j) {
            tomb[i][j] = rand();
        }
    }

    for (int i = 0; i < n; ++i) {
        for (int j = 0; j < m; ++j) {
            printf("%d;", tomb[i][j]);
        }
        printf("\n");
    }

    printf(";\n");

    for (int i = 0; i < m; ++i) {
        for (int j = 0; j < n; ++j) {
            printf("%d;", tomb[j][i]);
        }
        printf("\n");
    }


    return 0;
}

Az alábbi videók a mátrix transzponálását illetve véletlen feltöltését mutatják be:

mx_tr

mx_rnd

A feladat megoldásához közvetlenül ugyan nem kapcsolódnak, de segíthetnek a mátrixkezelés gyakorlásában. A két kapcsolódó videó a mátrix feltöltését illetve kiírását mutatják be mind fájlból, illetve fájlba történő műveletekkel.

mx_rf

mx_wf

Feladat (f0167)

Problémafelvetés:

Határozd meg egy \(N \times M\)-es véletlen értékű elemekkel feltöltött egész mátrix minden egyes sorának és oszlopának minimumát és maximumát is.

Specifikáció:

Az input két egész szám NxM alakban, ahol \(N\) és \(M\) (mindkettő maximum 128) a mátrix sorainak és oszlopainak száma.

Az output .csv formátumú (pontosvesszővel elválasztott) táblázat. Ennek az első és második sorába az oszlopok minimális illetve maximális elemeit, első és második oszlopába a sorok minimális és maximális elemeit kell kiírni. A harmadik sor illetve harmadik oszlop üres, kivéve az első két-két cellát, melyek értéke a "Min." és "Max." szöveg. A mátrix első sorának első eleme a táblázat negyedik sorának negyedik oszlopába kerül. A táblázat bal felső sarkában \(2\times 2\) cella üresen marad. Például 3x3 input esetén:

1
2
3
4
5
6
;;"Min.";1;2;3
;;"Max.";7;8;9
"Min.";"Max."
1;3;;1;2;3
4;6;;6;5;4
7;9;;7;8;9

Algoritmustervezés/Megvalósítás:

A .csv a Comma Separated Values rövidítése. Ez a formátum tetszőleges számú sort tartalmaz, ahol egy sort vesszővel (vagy tágabb értelemben véve pontosveszővel, kettősponttal vagy tabulátorral) elválasztott elemek sorozata alkot. A sor utolsó eleme után nem kötelező az elválasztó jel.

Lehetséges megoldás (m0167)
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
/*
 * Szegedi Tudományegyetem
 * Informatikai Tanszékcsoport
 * Szoftverfejlesztés Tanszék
 *
 * Programozás Alapjai feladat megoldása
 *
 * Gergely Tamás, 2018. őszi félév.
 *
 * Algoritmustervezés/Megvalósítás:
 *    Mivel tömbökkel dolgozunk, aminek a túlindexelése veszélyes a
 *    memóriára, a megadott méreteket megvizsgáljuk. Ezután az eleve
 *    lefoglalt 128x128-as mátrix megfelelő (NxM-es) részét feltöltjük
 *    0 és 99 közötti véletlen elemekkel. A sor- és oszlopminimumokat és
 *    -maximumokat a sor illetve oszlop első elemével tudjuk biztonságosan
 *    inicializálni. Ezután végigmegyünk a mátrix minden elemén és
 *    megnézzük, hogy ő-e a minimum/maximum a sorában és/vagy az oszlopában.
 *
 * fordítás:
 *    gcc -o m0167 m0167.c
 *
 * futtatás:
 *    ./m0167
 */

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#define MAX_VALUE 100
#define MAX_SIZE  128

int matrix[MAX_SIZE][MAX_SIZE];

int main() {
    int N = 0, M = 0, i, j;
    int omin[MAX_SIZE], omax[MAX_SIZE], smin[MAX_SIZE], smax[MAX_SIZE];
    scanf("%dx%d", &N, &M);
    /* Ellenőrzés, hogy elkerülhessük a tömb alul- illetve túlindexelését */
    if (N < 1 || M < 1 || MAX_SIZE < N || MAX_SIZE < M) {
        fprintf(stderr, "A mátrix mérete legalább 1x1 és legfeljebb %dx%d lehet!\n", MAX_SIZE, MAX_SIZE);
        return 1;
    }
    /* Mátrix feltöltése */
    srand(time(NULL)); // Hogy ne mindig ugyanazt a véletlen sorozatot kapjuk ;)
    for (i = 0; i < N; ++i) {
        for (j = 0; j < M; ++j) {
            matrix[i][j] = rand() % MAX_VALUE;
        }
    }

    /* Sor minimum és maximum inicializálás*/
    for (i = 0; i < N; ++i) {
        smin[i] = matrix[i][0];
        smax[i] = matrix[i][0];
    }

    /* Oszlop minimum és maximum inicializálás*/
    for (j = 0; j < M; ++j) {
        omin[j] = matrix[0][j];
        omax[j] = matrix[0][j];
    }

    /* Sor és oszlop minimum és maximum számítás */
    for (i = 0; i < N; ++i) {
        for (j = 0; j < M; ++j) {
            int mij = matrix[i][j];
            if (mij < smin[i]) {
                smin[i] = mij;
            } else if (mij > smax[i]) {
                smax[i] = mij;
            }
            if (mij < omin[j]) {
                omin[j] = mij;
            } else if (mij > omax[j]) {
                omax[j] = mij;
            }
        }
    }

    /* Kiíratás */
    // Az első sor elemei ...
    printf(";;\"Min.\"");
    for (j = 0; j < M; ++j) {
        // ... az oszlopminimumok.
        printf(";%d", omin[j]);
    }
    // A második sor elemei ...
    printf("\n;;\"Max.\"");
    for (j = 0; j < M; ++j) {
        // ... az oszlopmaximumok.
        printf(";%d", omax[j]);
    }
    // A harmadik sor fix.
    printf("\n\"Min.\";\"Max.\"\n");
    // A további sorok ...
    for (i = 0; i < N; ++i) {
        // ... minimuma és maximuma ...
        printf("%d;%d;", smin[i], smax[i]);
        for (j = 0; j < M; ++j) {
            // ... majd a sor elemei.
            printf(";%d", matrix[i][j]);
        }
        printf("\n");
    }
    return 0;
}

Feladatok

Feladat (f0051)

Problémafelvetés:

Egy számtani sorozat első két tagja \(A_1\) és \(A_2\). Számítsa ki a sorozat \(N\)-dik tagját!

Feladat (f0053)

Problémafelvetés:

Egy mértani sorozat első két tagja \(A_1\) és \(A_2\). Számítsa ki a sorozat \(N\)-dik tagját!

Feladat (f0124)

Problémafelvetés:

Írj programot, amelyben a felhasználónak egy, a program által meghatározott számot kell kitalálnia! A felhasználó tippjét a program vagy elfogadja, vagy megmondja, hogy a gondolt szám annál kisebb vagy nagyobb, és kéri a következő tippet!

Algoritmustervezés/Megvalósítás:

A kitalálandó szám és a tippelt érték viszonyát egy külön függvény határozza meg, de a kiírás már a főprogramban történjen! Készíts többféle algoritmust és megvalósítást. Először oldd meg a feladatot megkötések nélkül, majd úgy, hogy az alábbi megszorítások közül kiválasztasz egyet, és azt betartod:

  • A for, while és do-while közül csak a for szerkezetet használhatod.
  • A for, while és do-while közül csak a while szerkezetet használhatod.
  • A for, while és do-while közül csak a do-while szerkezetet használhatod.
Feladat (f0171)

Problémafelvetés:

Egy könyvelő minden év végén statisztikát készít az általa kezelt cégek havi eredményességéről. A cégek havi bevétele és kiadása alapján kiszámolja a:

  • minimális, maximális és átlagos havi bevételt,
  • minimális, maximális és átlagos havi kiadást,
  • minimális, maximális és átlagos havi pénzügyi eredményt,
  • a relatív pénzügyi eredményesség (profit) alapján a hónapok rangsorát.

Specifikáció:

Az input egy 2 oszlopot és 12 sort tartalmazó, elválasztó karakterként ';'-t használó .csv fájl. Az első oszlop a havi bevétel, a második a havi kiadás, az n. sor az n. hónap értékeit tartalmazza.

Az output egy ';'-vel elválasztott .csv fájl. Az első 4 sora a havi bevétel, kiadás és pénzügyi eredmény statisztikáit mutatja. Az első sor első cellája üres, a következő három oszlopban rendre a "min.", "max." és "átlag" szövegek szerepelnek. A következő három sorban az első oszlop a "Bevétel", "Kiadás" és "Eredmény" szövegeket, a következő 3 oszlop rendre a kiszámított minimális, maximális és átlagos értékeket tartalmazza. Az 5. sor üres, a 6. sor első három cellája a "Hónap", "Profit%" és "Eredmény" szövegeket tartalmazza. A következő 12 sor rendre a hónapok neveit, valamint a hozzájuk tartozó profit (eredmény/bevétel) százalékos értékét (két tizedesjegy pontossággal) és a pénzügyi eredményt tartalmazza, a profit szerint csökkenő sorrendben. Profitegyenlősség esetén a magasabb eredmény kerül előbbre, ha az is egyenlő, akkor a korábbi hónap.

Feladat (f0202)

Problémafelvetés:

Írj egy programot, ami bekéri a tér 4 pontjának koordinátáit, és számítsd ki az általuk meghatározott test felszínét. Feltehető, hogy a 4 pont nem egy síkba esik.

Algoritmustervezés/Megvalósítás:

A főprogram csak az input/output műveleteket végezze, a számolást külön függvény(ek)ben oldd meg. Az adatok tárolására használj összetett adatszerkezetet, ha van értelme.

Feladat (f0204)

Problémafelvetés:

Írj egy programot, ami adott nehézségi gyorsulás (\(g=9,81 m/s^2\)) mellett szimulálja egy \(\alpha\) kilövési szögben és \(v\) kezdősebességgel kilőtt test röppályáját légüres térben sík terepen. A felhasználó adja meg a kilövési paramétereken túl azt is, hogy a kezdő és végpontokon kívül teljes repülési időt hány egyenlő részre osztva kell meghatározni és kiírni a test helyzetét (\(x,y\) koordinátáit).

Algoritmustervezés/Megvalósítás:

A főprogram csak az input/output műveleteket végezze, a számolást külön függvény(ek)ben oldd meg. Az adatok tárolására használj összetett adatszerkezetet, ha van értelme.

További gyakorló feladatok

Feladat (f0118)

Feladat:

Írj programot, mely bekér egy természetes számot, és kiszámítja a Fibonacci-sorozat n-ik elemét! A Fibonacci-sorozat nulladik és első eleme 1, a többi pedig az őt megelőző két elem összege. Készíts kétféle algoritmust két külön függvényben, és mindkettővel számoltasd ki az eredményt:

  • Az első egy rekurzív algoritmus legyen.
  • A másik átgondolt, ciklust használó algoritmus legyen.

Hasonlítsd össze a két megoldás futásidejét!

Lehetséges megoldás (videó)

Az alábbi videó a Fibonacci-sorozat elemeinek rekurzív kiszámítását mutatja be:

f0118

Feladat (f0135)

Problémafelvetés:

Készíts egy programot, amely kiírja egy \(4 \times 4\)-es egész mátrix transzponáltját.

Specifikáció:

Az input 16 darab -99999 és 99999 közötti egész szám, a mátrix elemei sorfolytonosan megadva. Az output a mátrix és a mátrix transzponáltja "szépen formázva", azaz 4 sorban 4-4 egymás alá igazított egész értékkel.

Feladat (f0195)

Problémafelvetés:

Írj egy programot, ami megmondja, hogy egy hallgató teljesítette-e a félévet programozás alapjai gyakorlatból.

Algoritmustervezés/Megvalósítás:

A főprogram csak az input/output műveleteket végezze, a számolást külön függvény(ek)ben oldd meg. Készíts egy adatszerkezetet, amiben a hallgató nevét, pontjait tároljuk. A program tároljon külön minden részpontszámot és a hiányzásokat is. A program a bemenet.txt nevű fájlból olvassa be az adatokat és a kimenet.txt nevű fájlba írja ki az eredményt.

Ötlet

Vedd elő az 5. gyakorlat f0194-es gyakorló feladatának általad írt megoldását, és azt alakítsd át!

Feladat (f0203)

Problémafelvetés:

Írj egy programot, ami bekéri a tér 4 pontjának koordinátáit, és számítsd ki az általuk meghatározott test felszínét. Feltehető, hogy a 4 pont nem egy síkba esik.

Algoritmustervezés/Megvalósítás:

A főprogram csak az input/output műveleteket végezze, a számolást külön függvény(ek)ben oldd meg. Az adatok tárolására használj összetett adatszerkezetet, ha van értelme. A program a bemenet.txt nevű fájlból olvassa be az adatokat és a kimenet.txt nevű fájlba írja ki az eredményt.

Ötlet

Vedd elő az f0202-es gyakorló feladat általad írt megoldását, és azt alakítsd át!

Feladat (f0205)

Problémafelvetés:

Írj egy programot, ami adott nehézségi gyorsulás (\(g=9,81 m/s^2\)) mellett szimulálja egy \(\alpha\) kilövési szögben és \(v\) kezdősebességgel kilőtt test röppályáját légüres térben sík terepen. A felhasználó adja meg a kilövési paramétereken túl azt is, hogy a kezdő és végpontokon kívül teljes repülési időt hány egyenlő részre osztva kell meghatározni és kiírni a test helyzetét (\(x,y\) koordinátáit).

Algoritmustervezés/Megvalósítás:

A főprogram csak az input/output műveleteket végezze, a számolást külön függvény(ek)ben oldd meg. Az adatok tárolására használj összetett adatszerkezetet, ha van értelme. A program a bemenet.txt nevű fájlból olvassa be az adatokat és a kimenet.txt nevű fájlba írja ki az eredményt.

Ötlet

Vedd elő az f0204-es gyakorló feladat általad írt megoldását, és azt alakítsd át!

Feladat (f0207)

Problémafelvetés:

Adott két körszerű test a kétdimenziós síkon. A testek helyzetét a középpontjaikkal azonosítjuk. Kezdetben a felhasználó mindkét testhez megadja:

  1. a középpontját \(x,y\) koordinátákkal,
  2. sugarát,
  3. kezdősebességét és annak irányát, valamint
  4. a test tömegét.

Megad továbbá egy \(\Delta t\) és egy \(T\) időintervallumot. A feladat a két test kétdimenziós fizikai mozgásának szimulálása a \(0..T\) időintervallumban \(\Delta t\) időbeosztással. Ez azt jelenti, hogy a kezdő időpontban kiszámoljuk a testekre ható erőket, majd a test mozgását a következő \(\Delta t\) időintervallumban úgy közelítjük, mintha a testre ebben az időintervallumban nem hatna egyéb erő. Ezután újra kiszámítjuk az erőket, majd újra közelítjük a mozgást, stb. A mozgás szimulálása a \(T\) időpontig vagy a két test "ütközéséig" tart. A pontosság kedvéért a két test helyzetét (\(x,y\) koordinátáit) úgy írassuk ki, mintha az origo, azaz a koordinátarendszer középpontja a két test közös tömegközéppontja lenne, azaz a kiszámított koordinátaértékeket ezek szerint korrigáljuk minden lépésben.

Algoritmustervezés/Megvalósítás:

A főprogram csak az input/output műveleteket végezze, a számolást külön függvény(ek)ben oldd meg. Az adatok tárolására használj összetett adatszerkezetet, ha van értelme. A program a bemenet.txt nevű fájlból olvassa be az adatokat és a kimenet.txt nevű fájlba írja ki az eredményt.

Ötlet

Vedd elő az 5. gyakorlat f0206-os gyakorló feladatának általad írt megoldását, és azt alakítsd át!

Kapcsolódó linkek


Utolsó frissítés: 2021-09-01 11:11:07