4. gyakorlat: programozási tételek és tömbök

Ez a gyakorlati óra a címben említett két témakört érinti. Az egyes feladatoknál gondoljuk át a következőket:

1 Osztók összege

Adjuk meg egy felhasználótól kért szám osztóinak összegét! (Pl. 6-hoz: 1+2+3+6 = 12.) Melyik programozási tételeket kell ehhez kombinálni? Nevezzük meg őket! Írjuk meg a programot úgy is, hogy az osztók összegébe a számot önmagát nem számítjuk bele! Hol kell ehhez módosítani a programot?

Tökéletes szám az, amelynél az utóbbi összeg (vagyis az osztók 1-et beleértve, de a számot magát nem) megegyezik magával a számmal. A 6 a legkisebb tökéletes szám (1+2+3=6). A következők 28 és 496. Írjuk ki, hogy a kapott szám tökéletes-e!

Megoldás

Az osztók összegzéséről egyből eszünkbe juthat az összegzés tétele: ciklus a számokon, akkumulátor változóban összegzés. Az összeghez azonban nem mindegyik számot kell hozzáadni, hanem csak az osztókat, vagyis válogatunk közülük. A kiválogatás tétele haszonlít a számláláshoz: ha egy feltétel teljesül, akkor csinálunk valamit a számmal:

CIKLUS AMÍG van még szám, ADDIG
    szám = következő elem
    HA feltétel(szám), AKKOR
       KIÍR: szám
    FELTÉTEL VÉGE
CIKLUS VÉGE
#include <stdio.h>

int main()
{
   int szam, oszto, osztoosszeg;

   printf("Szam: ");
   scanf("%d", &szam);

   osztoosszeg=0;
   /* oszto>szam/2 mar nincsen (csak sajat maga lenne) */
   for (oszto=1; oszto<=szam/2; oszto+=1)
      if (szam%oszto==0)
         osztoosszeg+=oszto;

   if (osztoosszeg==szam)
      printf("Ez egy tokeletes szam.\n");
   else
      printf("Nem tokeletes szam, %d != %d.\n", szam, osztoosszeg);

   return 0;
}

2 Az év napja

Írjunk programot, amelyik egy adott dátumról (év, hónap, nap) kiírja, hogy az év hányadik napja! Az év paraméterre a szökőévek miatt van szükség. Szökőév minden negyedik, nem szökőév minden századik, de szökőév minden 400-adik. 2000-ben ezért volt szökőév.

Megoldás

#include <stdio.h>

int main()
{
    /* a program altal hasznalt tablazat */
    int honapok[]={31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
    /* ehhez fogja kiszamolni a napot */
    int ev=2013, honap=2, nap=4;
    /* segedvaltozok */
    int szokoev, hanyadik, i;

    /* az adott honapnal mar nem kell hozzaadni a napok szamat,
       ezert i<honap. A tomb viszont 0-tol szamozodik, ezert
       kivonok az indexbol mindig 1-et! Ha a januar napjait adom
       hozza epp, akkor a 0-s indexu elem kell. */
    hanyadik=0;
    for (i=1; i<honap; i+=1)
		hanyadik+=honapok[i-1];
    /* akkor szokoev, ha oszthato 400-zal VAGY (oszthato 4-gyel ES
       nem oszthato 100-zal). ha szokoev van, es a februar mar eltelt,
       akkor +1 nap (mert csak 28-at adtunk hozza, de 29-et kellene */
    szokoev=ev%400==0 || (ev%100!=0 && ev%4==0);
    if (szokoev && honap>2)
		hanyadik+=1;
	/* vegul a mostani honapbol eltelt napok */
    hanyadik+=nap;

    printf("%4d.%02d.%02d. az ev %d. napja.\n",
        ev, honap, nap, hanyadik);

    return 0;
}

Figyelni kell itt, hogy mindig a hónap-1 indexet használjuk a tömbön, mivel a január, az 1. hónap adata a 0. indexű elemben van. Ezt úgy is meg lehetne oldani, ha a 0. indexű elembe betennénk egy helytartót, pl. egy 0 értéket. Mert akkor a januárhoz tartozó 31-es szám az 1. indexre kerülne, a február 28-a a 2. indexre stb.

int honapok[]={0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

3 Statisztika a számokról

Írjunk programot, amelyik a szabványos bemenetről olvas, fájl vége jelig. Számolja meg, hogy a beírt számok közül, amelyek 1 és 10 között vannak, melyik hányszor szerepelt! Írja ki ezt a szabványos kimenetre a következő formában:

1: 5 db
2: 3 db
3: 4 db
...

Megoldás

Az egyes számjegyeket, mivel tudjuk, hogy összesen pontosan 10 van belőlük, egy tömbben lehet tárolni. A bemenet végigolvasására egy ciklust írunk. Itt a számlálás tételét kell megvalósítani, csak 10 darab számlálóval. Hogy melyik számlálóról van szó, azt a tömb indexelésével választjuk ki. Mivel a beérkező számok az 1…10 tartományban vannak, a tömb viszont 0…9 tartományban indexelődik, ezért az indexeléshez mindig a szám−1 értéket használjuk. (Kényelmi okokból, hogy mindenhol ugyanilyen legyen az indexelés, a számlálók nullázása és a kiírás is ezt az elvet követi a programban.)

#include <stdio.h>

int main()
{
    int t[10], i;
    int c;

    for (i=1; i<=10; i+=1)            /* nullazas */
        t[i-1]=0;

    printf("Irj be 1 es 10 kozotti szamokat!\n");
    /* amíg sikerül számot beolvasni */
    while (scanf("%d", &c)==1) {    /* feldolgozas */
        if (c>=1 && c<=10)
            t[c-1]+=1;
        else
            printf("1 es 10 kozotti szamokat adj meg!\n");
    }

    for (i=1; i<=10; i+=1)            /* kiiras */
        printf("%d: %d\n", i, t[i-1]);

    return 0;
}

A scanf() a beolvasott számokon kívül is ad egy számot vissza. Ezt ellenőrizve megtudhatjuk, hogy hány konverzió sikerült. A lenti kódban egy konverzió van (%d, egy szám); ha az sikerül, akkor a scanf() értéke 1 lesz. Vagyis a ciklus addig fut, amíg van beolvasott szám. Fájl vége jelnél a kapott érték nem lesz egy, mivel nincs már beolvasott szám. („Fájl vége jel” – az első laboron szerepelt!)

4 Bankautomata

Pénzvisszaadós automatába kell egy olyan programrészt írnunk, amelyik a visszajárót számolja ki. Írjunk egy programrészt, amely egy adott pénzösszegről kiírja, hogy hogyan lehet azt a legkevesebb papírdarabbal/fémkoronggal kiadni!
Például: 1415 Ft = 1000 Ft + 2×200 Ft + 10 Ft + 5 Ft.

Megoldás

#include <stdio.h>

int main()
{
    /* nullaval jelzem a tomb veget */
    int penzek[]={20000, 10000, 5000, 2000, 1000, 500,
                  200, 100, 50, 20, 10, 5, 0};
    int mennyit, i;

    printf("Mennyi a visszajaro? ");
    scanf("%d", &mennyit);

    printf("Az automata kiadja:\n");
    /* feltetelezzuk, hogy a tombben csokkeno sorrendben vannak
       benne a bankjegyek. eloszor a legnagyobbol probalunk adni. */
    for (i=0; penzek[i]!=0; i+=1) {
        if (mennyit>=penzek[i]) {
            int db=mennyit/penzek[i];

            printf("%d db %d Ft-os.\n", db, penzek[i]);
            /* a darabot visszaszorozva megvan az osszeg,
               amit kiadtunk ebben a lepesben */
            mennyit-=db*penzek[i];
        }
    }
    if (mennyit!=0)
        printf("Nincs mar ilyen kicsi erme: %d Ft\n", mennyit);
    return 0;
}

A megoldásban csökkenő sorrendben vizsgáljuk a címleteket, és mindegyikből kiadunk annyit, amennyit lehet. A csökkenő sorrendet úgy állítjuk elő, hogy a tömböt, amely a címleteket tartalmazza, eleve csökkenő sorrendbe rendezve építjük be a programba. Ez egy ún. mohó algoritmus – mindig a legnagyobbat próbálja lépni a megoldás felé.

Egy tömbről általában nekünk kell megjegyeznünk a méretét, vagyis hogy hány elemet tartalmaz. Itt azonban lehet kicsit trükközni. A tömb utolsó eleme egy nullás szám, az jelöli meg a végét. A ciklus futási feltétele penzek[i]!=0, amelyben nem szerepel a tömb mérete. Azért tudjuk ezt megtenni, mert a nulla ebben a tömbben értelmetlen adat: nincs nulla forintos bankjegy. Ugyanígy a −1 is jó lenne, vagy bármi, amit az értékes adatoktól meg tudunk különböztetni.

A mennyit/penzek[i] kifejezés értéke egy egész szám, pl. 2100/1000=2. Az egész/egész osztás C-ben egész számot eredményez! Ezt ki is használjuk a programban.

5 Eratoszthenész szitája

     2  3  4  5
  6  7  8  9 10
 11 12 13 14 15
 16 17 18 19 20
 21 22 23 24 25

Eratoszthenész szitája prímszámokat keres. A módszer a következő. Felírjuk a számokat valameddig. 2 prímszám, ezt megjegyezzük. Kihúzzuk a többszöröseit, mivel azok nem prímszámok. Ezután 3 a következő, ami még nincs kihúzva. Az is prímszám, mivel nem találtunk ezidáig osztót hozzá: a nála kisebb összes szám többszöröseit kihúztuk, nála nagyobb osztója pedig nem lehet. A többszörösei viszont nem prímek: kihúzzuk az összes 3-mal oszthatót. 4-et már kihúztuk (2×2). 5 a következő prím, kihúzzuk n×5-öt stb. Írjuk ki ez alapján a prímszámokat 999-ig!

Megoldás

A tömb elemei itt logikai értékek. A tömböt pedig mindig magával a számmal indexeljük; prim[2] pl. azt tárolja, hogy a 2 prímszám-e. Az első két elemet így ugyan nem használjuk, de a program egyszerűbb, mert a tömbindex megegyezik magával a számmal. A prim[1000] méretű tömbben így 999-ig lehet megkeresni a prímeket.

#include <stdio.h>

int main()
{
    int prim[1000];
    int i;

    /* uresen indulunk - mindent primszamnak tekintunk */
    for (i=0; i<1000; i+=1)
        prim[i]=1;

    for (i=2; i<1000; i+=1)             /* megyunk a szitan */
        if (prim[i]) {                  /* es amit talalunk primet */
            int szam;

            printf("%d ", i);
            for (szam=i*2; szam<1000; szam+=i)
                prim[szam]=0;
        }
    printf("\n");

    return 0;
}

A többszörösök kihúzására ilyen ciklus is elképzelhető:

int szorzo;

szorzo=2;
while (i*szorzo<1000) {
   prim[i*szorzo]=0;   /* a tobbszorosei nem primek. */
   szorzo+=1;
}