13. TANANYAG · FORRÁSKÓD

Tömbök és foreach

13_tombok_kommentelt.cs
13_tombok_kommentelt.csAnimáció megnyitása →
namespace _13_Tombok // A namespace (névtér) összefogja az ehhez a példához tartozó kódelemeket; ennek a névtérnek a neve _13_Tombok.
{ // Itt kezdődik a névtér kódblokkja.
    internal class Program // A Program nevű osztály deklarációja; ebben lesz a Main és a tömbökkel dolgozó metódusok.
    { // Itt kezdődik a Program osztály törzse.
        static void Main(string[] args) // A Main a program belépési pontja. Figyeld meg: a string[] args is egy tömb!
        { // Itt kezdődik a Main metódus törzse.
            // MIÉRT KELL TÖMB?
            // A Napelem dolgozatban 30 nap termelését számoltuk ki egy ciklusban. Az összeg és az átlag megvolt,
            // de a ciklus után a napi értékek ELVESZTEK: minden kör felülírta az előzőt.
            // Ha azt kérdezzük, "melyik napokon volt az átlagnál több", ahhoz az összes napi értékre szükség van.
            // A tömb sok, azonos típusú értéket tárol EGY névvel, sorszámozott (indexelt) helyeken.

            Random random = new Random(); // Egyetlen Random objektum a véletlen hőmérsékletekhez.

            // 1. PÉLDA – TÖMB LÉTREHOZÁSA KEZDŐÉRTÉKEKKEL.

            string[] napok = { "hétfő", "kedd", "szerda", "csütörtök", "péntek", "szombat", "vasárnap" }; // string[]: szövegek tömbje. A { } között felsorolt elemekkel jön létre, így a mérete 7.
            Console.WriteLine($"A napok tömb elemszáma: {napok.Length}"); // A Length tulajdonság megadja az elemek számát (Pythonban: len(napok)).

            // 2. PÉLDA – INDEXELÉS: AZ ELEMEK ELÉRÉSE.

            Console.WriteLine($"Az első elem (index 0): {napok[0]}"); // Az indexelés 0-tól indul: a napok[0] a hétfő.
            Console.WriteLine($"Az utolsó elem (index {napok.Length - 1}): {napok[napok.Length - 1]}"); // Az utolsó elem indexe mindig Length - 1, itt 6.
            // Console.WriteLine(napok[7]); // HIBA futás közben: IndexOutOfRangeException – 7 elemnél a legnagyobb index a 6.

            int sorszam = 3; // A felhasználó és a feladatszöveg SORSZÁMMAL beszél: "a 3. nap".
            Console.WriteLine($"A(z) {sorszam}. nap: {napok[sorszam - 1]}\n"); // Sorszámból indexet így kapunk: index = sorszám - 1, tehát a 3. nap a napok[2], vagyis szerda.

            // 3. PÉLDA – ÜRES TÖMB LÉTREHOZÁSA new-val, MEGADOTT MÉRETTEL.

            double[] homersekletek = new double[napok.Length]; // 7 darab double férőhely jön létre. A méret a létrehozás után már NEM változtatható.
            Console.WriteLine($"Létrehozás után az első elem: {homersekletek[0]}\n"); // Az új double tömb minden eleme 0 – ez az alapértelmezett érték.

            // 4. PÉLDA – FELTÖLTÉS for CIKLUSSAL.

            for (int i = 0; i < homersekletek.Length; i++) // Az i végigmegy a 0, 1, ..., 6 indexeken. A feltétel i < Length, NEM i <= Length!
            { // Itt kezdődik a for ciklus törzse.
                homersekletek[i] = random.Next(-50, 151) / 10.0; // -50..150 közötti egész, osztva 10.0-val: -5,0..15,0 fok egy tizedesjeggyel. Az i. helyre írjuk.
            } // A ciklus végére mind a 7 hely kapott értéket.

            // 5. PÉLDA – KIÍRÁS for CIKLUSSAL: SORSZÁM, NAP, ÉRTÉK.

            Console.WriteLine("Heti hőmérsékletek:"); // Fejléc a táblázathoz.
            for (int i = 0; i < napok.Length; i++) // Ugyanaz az i index mindkét tömbben ugyanahhoz a naphoz tartozik.
            { // Itt kezdődik a for ciklus törzse.
                Console.WriteLine($"{i + 1}. nap ({napok[i]}): {homersekletek[i]:f1} °C"); // Kiíráskor indexből sorszám lesz: i + 1. Az :f1 egy tizedesjegyre formáz.
            } // A ciklus után a táblázat minden sora kint van.

            Console.WriteLine($"Így NEM lehet kiírni egy tömböt: {homersekletek}\n"); // A tömböt egyben kiírva csak a típusa jelenik meg (System.Double[]), az elemei nem.

            // 6. PÉLDA – BEJÁRÁS foreach CIKLUSSAL: ÖSSZEG ÉS ÁTLAG.

            double osszeg = 0; // Ebben gyűjtjük az elemek összegét. A 0 kezdőérték kötelező.
            foreach (double homerseklet in homersekletek) // A foreach sorban felveszi a tömb minden elemét a homerseklet változóba. Pythonban: for homerseklet in homersekletek:
            { // Itt kezdődik a foreach ciklus törzse.
                osszeg += homerseklet; // Hozzáadjuk az aktuális elemet az összeghez. Index itt nem kell, ezért kényelmesebb a for-nál.
                // homerseklet = 0; // HIBA fordításkor: a foreach ciklusváltozója csak olvasható, a tömb elemét nem lehet így módosítani.
            } // A ciklus végén az osszeg mind a 7 elem összege.

            double atlag = osszeg / homersekletek.Length; // Az átlag: összeg / darabszám. Az osszeg double, ezért nem egészosztás történik.
            Console.WriteLine($"A heti átlaghőmérséklet: {atlag:f2} °C"); // Kiírjuk az átlagot két tizedesjeggyel.

            // 7. PÉLDA – EZÉRT KELLETT TÁROLNI: AZ ÁTLAG FELETTI NAPOK.
            // Az átlagot csak az ÖSSZES elem ismeretében tudjuk. Ezért az adatokon KÉTSZER kell végigmenni:
            // először kiszámoljuk az átlagot, utána megnézzük, mely napok voltak felette. Tömb nélkül ez nem menne.

            Console.WriteLine("Az átlagnál melegebb napok:"); // Fejléc a listához.
            for (int i = 0; i < homersekletek.Length; i++) // Itt for kell, nem foreach: a nap NEVÉHEZ az index kell, ami a napok tömbben is érvényes.
            { // Itt kezdődik a for ciklus törzse.
                if (homersekletek[i] > atlag) // Ha az i. nap hőmérséklete az átlag felett van...
                { // Itt kezdődik az if blokk.
                    Console.WriteLine($"\t{napok[i]}: {homersekletek[i]:f1} °C"); // ...kiírjuk a nap nevét és a hőmérsékletet. A \t tabulátorral beljebb kezdi a sort.
                } // Itt ér véget az if blokk.
            } // Itt ér véget a for ciklus.

            // 8. PÉLDA – TÖMB ÉS METÓDUS.
            // A kiírásra mostantól többször is szükség lesz, ezért metódusba tesszük.

            Console.WriteLine("\nKiderült, hogy a hőmérő 1 fokkal kevesebbet mutatott. Javítás után:"); // A történet: minden mért értéket korrigálni kell.
            Korrigal(homersekletek, 1.0); // A metódus megkapja a tömböt, és az ELEMEIT módosítja. Nem ad vissza semmit (void)...
            Kiir(napok, homersekletek); // ...mégis a javított értékek jelennek meg: a metódus ugyanazt a tömböt módosította, nem egy másolatot.

            double[] fahrenheit = Fahrenheitre(homersekletek); // Ez a metódus NEM módosítja a kapott tömböt, hanem egy ÚJ tömböt hoz létre és ad vissza.
            Console.WriteLine($"\nHétfő: {homersekletek[0]:f1} °C = {fahrenheit[0]:f1} °F"); // Az eredeti tömb (Celsius) megmaradt, mellette létezik az új (Fahrenheit).
        } // Itt ér véget a Main metódus törzse.

        static void Kiir(string[] napok, double[] homersekletek) // Két tömböt kap paraméterként; a [] jelzi, hogy tömböt vár. Nem ad vissza értéket.
        { // Itt kezdődik a Kiir() metódus törzse.
            for (int i = 0; i < napok.Length; i++) // Ugyanaz a ciklus, mint az 5. példában – most egy helyen, névvel ellátva.
            { // Itt kezdődik a for ciklus törzse.
                Console.WriteLine($"{i + 1}. nap ({napok[i]}): {homersekletek[i]:f1} °C"); // Sorszám, nap neve, hőmérséklet.
            } // Itt ér véget a for ciklus.
        } // Itt ér véget a Kiir() metódus.

        static void Korrigal(double[] homersekletek, double eltolas) // A kapott tömb minden eleméhez hozzáadja az eltolást. void: nincs visszatérési érték.
        { // Itt kezdődik a Korrigal() metódus törzse.
            for (int i = 0; i < homersekletek.Length; i++) // Módosítani csak for ciklussal, index segítségével lehet (foreach-csel nem).
            { // Itt kezdődik a for ciklus törzse.
                homersekletek[i] += eltolas; // Az i. elem új értéke: a régi érték + eltolás. Ez a hívó oldalon lévő tömböt módosítja!
            } // Itt ér véget a for ciklus.
        } // Itt ér véget a Korrigal() metódus.

        static double[] Fahrenheitre(double[] celsius) // A visszatérési típus double[]: a metódus egy egész tömböt ad vissza.
        { // Itt kezdődik a Fahrenheitre() metódus törzse.
            double[] eredmeny = new double[celsius.Length]; // Új, ugyanakkora tömb az eredményeknek.
            for (int i = 0; i < celsius.Length; i++) // Végigmegyünk a kapott tömb indexein.
            { // Itt kezdődik a for ciklus törzse.
                eredmeny[i] = celsius[i] * 9 / 5 + 32; // Átváltás: °F = °C · 9 / 5 + 32. Az eredeti tömb érintetlen marad.
            } // Itt ér véget a for ciklus.
            return eredmeny; // Visszaadjuk az új tömböt a hívónak.
        } // Itt ér véget a Fahrenheitre() metódus.
    } // Itt ér véget a Program osztály törzse.
} // Itt ér véget a _13_Tombok névtér.


/*
 * MI AZ A TÖMB?
 * -------------
 *
 * A tömb azonos típusú értékek sorozata, egyetlen név alatt.
 * Az elemeket a sorszámuk, vagyis az INDEXÜK alapján érjük el.
 *
 *     double[] homersekletek = new double[7];
 *
 *     index:     [0]    [1]    [2]    [3]    [4]    [5]    [6]
 *              ┌──────┬──────┬──────┬──────┬──────┬──────┬──────┐
 *     érték:   │  7,3 │  9,1 │ -2,0 │ 12,4 │  4,4 │ 10,0 │  6,8 │
 *              └──────┴──────┴──────┴──────┴──────┴──────┴──────┘
 *     sorszám:   1.     2.     3.     4.     5.     6.     7.
 *
 * Három dolgot jegyezz meg rögtön:
 *
 *     1. Minden elem UGYANOLYAN típusú (itt mind double).
 *     2. A méret a létrehozáskor dől el, és utána NEM változik.
 *     3. Az indexelés 0-tól indul, az utolsó index: Length - 1.
 *
 * Mikor kell tömb?
 *
 *     Ha az adatokra a feldolgozás során KÉSŐBB, vagy TÖBBSZÖR
 *     is szükség van. Például:
 *
 *         - az átlag feletti elemek listázásához (előbb átlag, aztán lista);
 *         - ha a "3. nap" értékét kell kiírni;
 *         - ha az adatokat többféleképpen is fel kell dolgozni.
 *
 *     Ha minden adatot csak egyszer kell felhasználni (például
 *     összegezni), akkor tömb nélkül is megoldható – ezt csináltuk
 *     a Napelem dolgozatban.
 */


/*
 * TÖMB LÉTREHOZÁSA – KÉT MÓD
 * --------------------------
 *
 * 1. Ha az elemeket már a kód írásakor ismerjük:
 *
 *     string[] napok = { "hétfő", "kedd", "szerda" };
 *
 *    A méretet a fordító számolja ki a felsorolásból (itt 3).
 *
 * 2. Ha csak a darabszámot tudjuk, az értékeket később kapjuk:
 *
 *     double[] homersekletek = new double[7];
 *
 *     double[]        homersekletek    =    new double[7];
 *        ↓                  ↓                     ↓
 *     double elemekből    a tömb             7 férőhelyes új tömb
 *     álló tömb típusa     neve              létrehozása
 *
 * Az új tömb elemei nem "üresek", hanem ALAPÉRTELMEZETT értéket kapnak:
 *
 *     Típus           Alapérték
 *     -----           ---------
 *     int, double     0
 *     bool            false
 *     char            '\0' (a 0 kódú karakter)
 *     string          null (vagyis: nincs benne szöveg)
 *
 * A méret változóval is megadható – ez a gyakoribb:
 *
 *     int db = 30;
 *     double[] termeles = new double[db];
 *
 * Vagy egy másik tömb méretéhez igazítva, ahogy a példában:
 *
 *     double[] homersekletek = new double[napok.Length];
 */


/*
 * INDEX ÉS SORSZÁM – A LEGGYAKORIBB HIBAFORRÁS
 * --------------------------------------------
 *
 * Az ember sorszámmal gondolkodik (1., 2., 3. nap),
 * a tömb indexszel (0, 1, 2).
 *
 *     sorszám → index:     index   = sorszám - 1
 *     index   → sorszám:   sorszám = index + 1
 *
 * Mikor melyik kell?
 *
 *     - a tömbhöz nyúlsz (olvasás, írás)      → INDEX
 *     - a felhasználónak írsz ki valamit       → SORSZÁM
 *
 *     Console.WriteLine($"{i + 1}. nap: {homersekletek[i]}");
 *                         ↑                           ↑
 *                     sorszám a                 index a
 *                     felhasználónak            tömbnek
 *
 * Ha a felhasználótól kérünk be egy napot:
 *
 *     int nap = BekerEgesz("Hányadik nap? ", 1, 7);   // 1..7 sorszám
 *     Console.WriteLine(homersekletek[nap - 1]);      // 0..6 index
 *
 * Túlindexelés:
 *
 *     homersekletek[7]     // 7 elemnél nincs ilyen!
 *
 * Ez a program futása közben IndexOutOfRangeException hibát
 * okoz, és a program leáll. Pythonban ennek az IndexError felel meg.
 *
 * C#-ban NINCS negatív index: a homersekletek[-1] nem az
 * utolsó elem (mint Pythonban), hanem szintén hiba.
 * Az utolsó elem: homersekletek[homersekletek.Length - 1].
 */


/*
 * A TÖMB BEJÁRÁSA for CIKLUSSAL
 * -----------------------------
 *
 * A tömb "bejárása" azt jelenti: sorban végigmegyünk az összes elemén.
 *
 *     for (int i = 0; i < homersekletek.Length; i++)
 *     {
 *         ... homersekletek[i] ...
 *     }
 *
 * Mindig ezt a formát használd:
 *
 *     - kezdés:      i = 0                (az első index)
 *     - feltétel:    i < tomb.Length      (KISEBB, nem kisebb-egyenlő!)
 *     - léptetés:    i++
 *
 * A < helyett írt <= a klasszikus hiba: az utolsó körben i == Length,
 * és az már túlindexelés.
 *
 * A tömb méretét ne írd be számmal (i < 7), hanem kérd el a tömbtől
 * (i < homersekletek.Length). Így ha a tömb mérete megváltozik,
 * a ciklus magától igazodik hozzá.
 *
 * Python megfelelője:
 *
 *     Python                                C#
 *     ------                                --
 *     for i in range(len(homersekletek)):   for (int i = 0; i < homersekletek.Length; i++)
 *         print(homersekletek[i])           {
 *                                               Console.WriteLine(homersekletek[i]);
 *                                           }
 */


/*
 * A foreach CIKLUS
 * ----------------
 *
 *     foreach (double homerseklet in homersekletek)
 *     {
 *         osszeg += homerseklet;
 *     }
 *
 *     foreach   (double     homerseklet     in     homersekletek)
 *        ↓          ↓            ↓           ↓           ↓
 *     "minden    az elem     ebbe kerül   "-ban/     ezt a tömböt
 *      elemre"    típusa     sorban az    -ben"      járjuk be
 *                            aktuális elem
 *
 * Python megfelelője szinte ugyanaz:
 *
 *     for homerseklet in homersekletek:
 *         osszeg += homerseklet
 *
 * A foreach kényelmes, mert nem kell index, és nem lehet túlindexelni.
 * De két dologra NEM alkalmas:
 *
 *     1. Az elem MÓDOSÍTÁSÁRA. A ciklusváltozó csak olvasható:
 *
 *            homerseklet = 0;      // fordítási hiba
 *
 *     2. Ha az INDEXRE is szükség van – például a sorszám kiírásához,
 *        vagy hogy egy MÁSIK tömb ugyanolyan indexű elemét is elérjük
 *        (napok[i] és homersekletek[i]).
 *
 * Döntési szabály:
 *
 *     Csak olvasom az elemeket, és az index nem kell   → foreach
 *     Módosítom az elemeket, vagy kell az index        → for
 */


/*
 * KÉT TÖMB, KÖZÖS INDEX
 * ---------------------
 *
 * A példában két tömb van:
 *
 *     napok:           "hétfő"  "kedd"  "szerda"  ...
 *     homersekletek:     7,3      9,1     -2,0    ...
 *     index:              0        1        2     ...
 *
 * Az összetartozó adatokat az INDEX köti össze: a napok[2] és a
 * homersekletek[2] ugyanarról a napról szól. Ezért kell a 7. példában
 * for ciklus – a foreach csak a hőmérsékletet adná, a nap nevét nem.
 *
 * Ezt a megoldást párhuzamos tömböknek hívjuk. A két tömbnek mindig
 * ugyanakkorának kell lennie, ezért hoztuk létre így:
 *
 *     double[] homersekletek = new double[napok.Length];
 */


/*
 * A TÖMBÖT NEM LEHET EGYBEN KIÍRNI
 * --------------------------------
 *
 * Pythonban:
 *
 *     print(homersekletek)      →   [7.3, 9.1, -2.0, ...]
 *
 * C#-ban:
 *
 *     Console.WriteLine(homersekletek);    →   System.Double[]
 *
 * A C# a tömb TÍPUSÁT írja ki, nem a tartalmát. Az elemeket egyenként,
 * ciklussal kell kiírni – ezért van a programban Kiir() metódus.
 */


/*
 * TÖMB ÁTADÁSA METÓDUSNAK
 * -----------------------
 *
 * Paraméterként:
 *
 *     static void Kiir(string[] napok, double[] homersekletek)
 *
 *   A típus után álló [] jelzi, hogy tömböt vár a metódus.
 *   Meghíváskor csak a tömb nevét adjuk át, [] nélkül:
 *
 *     Kiir(napok, homersekletek);
 *
 * Visszatérési értékként:
 *
 *     static double[] Fahrenheitre(double[] celsius)
 *     {
 *         double[] eredmeny = new double[celsius.Length];
 *         ...
 *         return eredmeny;
 *     }
 *
 *     double[] fahrenheit = Fahrenheitre(homersekletek);
 *
 * FONTOS: A METÓDUS AZ EREDETI TÖMBÖT KAPJA MEG, NEM MÁSOLATOT!
 *
 * Egy int paraméternél a metódus a szám MÁSOLATÁT kapja; ha
 * megváltoztatja, a hívó változója nem változik.
 *
 * A tömb más: a metódus ugyanazt a tömböt látja, mint a hívó.
 * Ha a metódus módosít egy elemet, az a hívónál is megváltozik.
 * Ezért működik a Korrigal(), pedig void, és nem ad vissza semmit.
 *
 *     Korrigal(homersekletek, 1.0);     // a homersekletek elemei megváltoznak
 *
 * A két minta tehát:
 *
 *     Korrigal()      → az átadott tömböt MÓDOSÍTJA       (void)
 *     Fahrenheitre()  → ÚJ tömböt hoz létre és ad vissza  (double[])
 *
 * Pythonban a lista ugyanígy viselkedik: ha egy függvény módosítja
 * a kapott listát, a hívó is a módosított listát látja.
 */


/*
 * TÖMB FELTÖLTÉSE BEKÉRÉSSEL
 * --------------------------
 *
 * A 12-es anyag ellenőrzött bekérését a tömb feltöltésére is
 * használhatjuk. A BekerValos() metódus ugyanaz, mint ott:
 *
 *     for (int i = 0; i < homersekletek.Length; i++)
 *     {
 *         Console.Write($"{napok[i]}: ");
 *         homersekletek[i] = BekerValos(-30, 45);
 *     }
 *
 * Ha a darabszámot is a felhasználó adja meg, a tömb a bekérés UTÁN
 * jön létre:
 *
 *     int db = BekerEgesz("Hány mérés volt? ", 1, 100);
 *     double[] meresek = new double[db];
 */


/*
 * TÖMB VAGY PYTHON-LISTA?
 * -----------------------
 *
 *     Python lista                     C# tömb
 *     ------------                     -------
 *     lista = [7.3, 9.1]               double[] t = { 7.3, 9.1 };
 *     len(lista)                       t.Length
 *     lista[0]                         t[0]
 *     lista[-1]                        t[t.Length - 1]
 *     lista.append(5)                  NINCS – a méret fix!
 *     vegyes típus lehet               minden elem azonos típusú
 *     print(lista)                     ciklussal kell kiírni
 *     IndexError                       IndexOutOfRangeException
 *
 * A C# tömb tehát NEM a Python-lista megfelelője. Arra a List<T>
 * való, amelyhez néhány hét múlva érünk el. A tömb akkor jó
 * választás, ha a darabszám előre ismert: 7 nap, 30 nap, 5 lottószám.
 */


/*
 * ÖSSZEFOGLALÁS
 * -------------
 *
 *     double[] t = new double[7];        7 elemű, minden elem 0
 *     string[] s = { "a", "b", "c" };     3 elemű, kezdőértékekkel
 *     t.Length                           elemszám
 *     t[0]   ...   t[t.Length - 1]       első ... utolsó elem
 *     index = sorszám - 1                a "3. nap" a t[2]
 *
 *     for (int i = 0; i < t.Length; i++)    ha kell az index vagy módosítasz
 *     foreach (double x in t)               ha csak olvasol
 *
 *     static void M(double[] t)          tömb paraméter (az eredetit kapja!)
 *     static double[] M(...)             tömb visszatérési érték
 *
 * Ellenőrző kérdések:
 *
 *     1. Mit ír ki?
 *            int[] t = new int[4];
 *            Console.WriteLine(t[2] + t.Length);
 *
 *     2. Mi a hiba?
 *            for (int i = 0; i <= t.Length; i++)
 *                Console.WriteLine(t[i]);
 *
 *     3. Egy 30 elemű tömbben hányas indexű a 15. nap? És az utolsó?
 *
 *     4. Miért nem fordul le?
 *            foreach (int x in t)
 *            {
 *                x = x * 2;
 *            }
 *        Írd át úgy, hogy működjön!
 *
 *     5. Mit ír ki a program?
 *            int[] a = { 1, 2, 3 };
 *            Duplaz(a);                       // minden elemet megszoroz 2-vel
 *            Console.WriteLine(a[0]);
 */