Tartalom
Az alábbi feladatokban egy egyszerű, kétdimenziós, egész számokból álló mátrixot fogunk feltölteni elemekkel. A különböző feltöltési feltételek és módszerek változatos feladatokká teszik ezt az alapvetően egyszerű tevékenységet.
9.1. feladat (Feltöltés billentyűzetről sorfolytonosan – szint: 1). Egy
méretű egész számokból álló mátrixot töltsünk fel sorfolytonosan billentyűzetről (először töltsük fel az első sorát, majd a második sorát, stb.)! A program összesen
értéket kérjen be. A megfogalmazásban az
méret csak példaképpen szerepel. A program a mátrix méretének változtatásával legyen képes eltérő méretekkel is működni! A program az adatbevitel végén jelenítse meg a mátrixban szereplő értékeket a képernyőn táblázatos formában!
Magyarázat: A mátrix méreteit (sor, oszlop) C# nyelven le lehet kérdezni a .GetLength() függvény segítségével, de kényelmesebb és elegánsabb megoldásnak tűnik, ha két konstanst definiálunk a sorok és oszlopok számához. A két konstans a program szövegében később helyettesítheti a konkrét méreteket:
9.1. forráskód. Konstansok használata
class Program { const int N = 5; const int M = 4; public static void Main() { // matrix letrehozasa int[,] m = new int[N,M]; } }
Az adatbekérést a Console.ReadLine függvény segítségével kell megoldani, melynek string típusú értékét esetünkben szám típusúra kell konvertálni. Az adatbekérést dupla ciklusba kell helyezni.
9.2. forráskód. A mátrix bekérése
class Program { const int N = 5; const int M = 4; public static void Main() { // matrix letrehozasa int[,] m = new int[N,M]; } }
A táblázatos megjelenítés során kalkuláljunk úgy, hogy a képernyőn 80 karakter jelenhet meg egy sorban, és 25 sorból áll a konzolablak! Feltételezzük, hogy a mátrixbeli számok sosem nagyobbak mint 999, így 3 karakteren elfér minden mátrixbeli szám. A Console.Write és Console.WriteLine képes formázottan kiírni számokat a képernyőre, pl. megadható, hogy a szám minimálisan hány karaktert foglaljon el a képernyőn. A szokásos „{0}” hivatkozást ki kell bővíteni a karakterek számával. A „{0,4}” alak azt jelenti, hogy a {0}. extra paramétert 4 karakter szélesen kell kiírni. Így a kiírás során az oszloposság biztosítható.
A teljes mátrixkiírás valahogy így néz ki (9.4. forráskód):
9.4. forráskód. Mátrix formázott kiírása
for(int i=0;i<N;i++) { for(int j=0;j<M;j++) { Console.Write("{0,4}",t[i,j]); } Console.WriteLine(); }
9.2. feladat (Feltöltés billentyűzetről folyamatos kijelzéssel – szint: 2). A sorfolytonos feltöltés közben minden teljes sor bevitele után a mátrix már kitöltött elemeit táblázatos alakban jelenítsük meg a képernyőn! Tehát pl. a 3. sor bevitele után az első 3 sor jelenjen meg!
Magyarázat: Ez a feladat az előzőtől annyival bonyolultabb, hogy a bekérésbe beágyazva kell megoldani a kiírást. Ha a bekérés ciklusait i és j változónevekkel jelöltük, akkor a kiírás ciklusait nem jelölhetjük ugyanezekkel. A kiírást módosítani kell, hogy az i. sorig jelenítse csak meg a mátrixot (a 9.5. forráskódban ez a k cikluson múlik).
9.5. forráskód. Mátrix kiírása bekérés közben
for(int i=0;i<N;i++) { // i. sor bekerese for(int j=0;j<M;j++) { Console.Write("Kerem {0},{1} elem erteket:",i,j); m[i,j] = int.Parse( Console.ReadLine() ); } // kiiras az i. sorig Console.WriteLine("----------------------------"); for(int k=0;k<=i;k++) { for(int l=0;l<M;l++) { Console.Write("{0,4}",t[k,l]); } Console.WriteLine(); } }
9.3. feladat (Feltöltés billentyűzetről – szint: 1). Egy
méretű egész számokból álló mátrixot töltsünk fel úgy billentyűzetről, hogy a felhasználó minden egyes adatbekérés során megadja sor- és oszlopkoordináták szerint, melyik mátrixelem értékét kívánja beírni, majd beírja magát a számértéket is! A program ügyeljen arra, hogy a mátrixelemek koordinátái a méreten belül maradjanak! A felhasználó a koordinátákat ne 0-tól, hanem 1-től számozva adhassa meg, vagyis
,
értékekkel.
Magyarázat: Ez esetben nincs szükség egymásba ágyazott ciklusokra, mivel a bekérést egyszerűen 20-szor kell megismételni. Ügyeljünk arra, hogy 20 helyes bekérést kell végrehajtani, ezért ha hibás koordinátákat adnak meg, akkor azt nem számoljuk bele a 20-ba (a 9.27. forráskód). Ne feledjük el, hogy a mátrix sorai és oszlopai a forráskódban 0-tól számozódnak, így a bekért koordinátákból 1-et le kell vonni!
9.6. forráskód. Adatbekérés koordinátákkal
int db=0; while (db<N*M) { Console.Write("Kerem a matrix sorat [1..{0}]:",N); int i = int.Parse( Console.ReadLine() ); Console.Write("Kerem a matrix oszlopat [1..{0}]:",M); int j = int.Parse( Console.ReadLine() ); Console.Write("Kerem az erteket:"); int x = int.Parse( Console.ReadLine() ); if (1<=i && i<=N && 1<=j && j<=M) { m[i-1,j-1] = x; db++; } else Console.Write("Na ezt megegyszer, hibas volt."); }
A probléma egy másik lehetséges kezelése, hogy amennyiben a felhasználó nem valós sor- és oszlopkoordinátát adna meg, úgy azt megismételtetjük vele, mindaddig, míg elfogadható értékeket kapunk. A bekérésnél mindig csak akkor lépünk a következő bekérésre, ha az előzőek már rendben voltak. Ekkor a tárolást megelőző if feltételvizsgálatra már nincs szükség. Valamint ekkor minden menetben sikeres adatbekérést hajtunk végre, így a külső 20-as ciklus átírható for ciklusra (lásd a 9.28. forráskód).
9.7. forráskód. Adatbekérés koordinátákkal v2.0
for (int db=0;db<<N*M;db++) { // sor int i; do { Console.Write("Kerem a matrix sorat [1..{0}]:",N); i = int.Parse( Console.ReadLine() ); } while (i<1 || i>N); // oszlop int j; do { Console.Write("Kerem a matrix oszlopat [1..{0}]:",M); j = int.Parse( Console.ReadLine() ); } while (j<1 || j>M); // ertek Console.Write("Kerem az erteket:"); int x = int.Parse( Console.ReadLine() ); // tarolas m[i-1,j-1] = x; }
Érdemes ez esetben függvényhívásokat szervezni az egyes részfeladatok megoldásának. Ez esetben a hibakijelzés is elegánsabban megoldható (lásd a 9.29. forráskód). A sor- és oszlopbekérő függvényt szándékosan két különböző megoldással készítettük el (9.29. forráskód).
9.8. forráskód. Adatbekérés függvények segítségével v3.0
static int sorBekeres() { int i; Console.Write("Kerem a matrix sorat [1..{0}]:",N); do { i = int.Parse( Console.ReadLine() ); if (i<1 || i>N) Console.WriteLine("Nem jo. Ujra!"); } while (i<1 || i>N); return i; } //................................................................ static int oszlopBekeres() { Console.Write("Kerem a matrix oszlopat [1..{0}]:",M); while(true) { int j = int.Parse( Console.ReadLine() ); if (1<=j && j<=M) return j; else Console.WriteLine("Nem jo. Ujra!"); } } //................................................................ static void Main() { ... for (int db=0;db<<N*M;db++) { int i=sorBekeres(); int j=oszlopBekeres(); Console.Write("Kerem az erteket:"); int x = int.Parse( Console.ReadLine() ); // tarolas m[i-1,j-1] = x; } }
9.4. feladat (Egyszerre több érték listásan – szint: 3). A mátrix feltöltése közben lehessen egy időben több értéket is megadni! Ennek során a kezelő először megadja a cella koordinátáját, majd vesszővel elválasztva több számértéket. Ezt úgy kell kezelni, hogy a számértékek közül az első az adott x,y cellába kerüljön, a következő érték a sorfolytonosan következő cellába, stb. Ha több számérték került volna be a felsorolásba, mint amennyit az adott sor képes fogadni, úgy a felesleges értékeket hagyjuk figyelmen kívül.
Magyarázat: Ha egy időben több számot is be lehet írni, akkor a bevitel nem int-ben fogadható, hanem string-ben. A string ekkor vesszőkkel (vagy más határolójelekkel) elválasztva tartalmazza a számokat. A stringből legegyszerűbben a split függvénnyel lehet tömböt készíteni. A split (szétvág) használatakor meg kell adni a határolójelet, amely mentén a stringet elemekre kell bontani. A kapott stringtömbben az eredeti stringet alkotó listaelemek fognak szerepelni. Ezeket már egyesével a parse segítségével fel lehet dolgozni (a 9.30. forráskód).
9.9. forráskód. String felbontása a Split segítségével
// pl "12,24,35" string s = Console.ReadLine(); string[] elemek = s.Split(’,’); // ez esetben az elemek vektor 3 elemű lesz // elemek[0] = "12" // elemek[1] = "24" // elemek[2] = "35"
9.5. feladat (Feltöltés folytatása I/N – szint: 2). A 9.3. feladatban megfogalmazott feltöltést módosítsuk annyiban, hogy a felhasználónak legyen lehetősége korábban is félbeszakítani! Ezt oldjuk meg úgy, hogy minden egyes mátrixelem beírása után jelenjen meg a Szeretne újabb adatot beírni I/N kérdés, melyre N válasz megadása esetén a bevitelből azonnal lépjen ki! A továbbiakban a program jelenítse meg a mátrixban szereplő értékeket a képernyőn táblázatos alakban!
Magyarázat: Az I/N választ bekérhetjük Console.ReadLine() segítségével is, de ekkor az I vagy N betű leütése után még Enter-t is kell ütni. Elegánsabb megoldás a Console.ReadKey() használata, mely ténylegesen egy billentyű leütésére vár, és eredményül egy ConsoleKeyInfo típusú értéket ad meg. Ez egy rekord, melynek különböző mezőiben szerepel nemcsak az, hogy melyik billentyűt ütötték le, de az is, hogy a módosítók (shift, control, alt) közül melyik volt eközben lenyomva (lásd a 9.31. forráskód).
9.10. forráskód. Console.ReadKey használata
ConsoleKeyInfo k = Console.ReadKey(); if (k.KeyChar == ’n’ || k.KeyChar == ’N’) Console.Write("NEM"); else if (k.KeyChar == ’i’ || k.KeyChar == ’I’) Console.Write("IGEN");
9.6. feladat (Módosítás ellenőrzése – szint: 3). A mátrix feltöltése közben előfordulhat, hogy a felhasználó egy olyan mátrixelem bevitelét végzi, amely már korábban kapott értéket. Ha ez előfordulna, úgy a koordináták beolvasása után írjuk ki a képernyőre, hogy Figyelem: ez az elem már kapott értéket. Valóban módosítani akarja I/N?! Ha a felhasználó I-t választ, úgy módosíthassa ezt az értéket, ellenkező esetben kérjünk újabb koordinátákat be!
Magyarázat: A mátrix minden egyes cellájában induláskor a típusának megfelelő nulla
érték szerepel. Double típusú mátrixnál a 0.0, int típusú mátrixnál 0, karakter
esetén a 0 kódú (\0
) karakter, bool mátrix esetén a false, stb. Ezt ki tudjuk használni
annak eldöntésére, hogy a mátrix adott cellája kapott-e már értéket korábban vagy
sem.
Sokkal bonyolultabb a helyzet, ha a mátrixba maga a 0 mint érték is bekerülhet a felhasználói adatbevitel során. Ekkor nem lehet elkülöníteni, hogy egy mátrixcellának azért 0 az értéke, mert még nem írtak be oda semmit, vagy azért 0, mert ezt az értéket írták be. Ekkor még választhatjuk, hogy a mátrixot előfeltöltjük, és egy olyan számértéket helyezünk a cellákba, amely nem a 0, de valamiért tudjuk, hogy nem fordulhat elő adatbevitel közben. Ez az érték lehet akár a -100 is, de gyakoribb, hogy ilyen közönséges értéket nem könnyű találni. Ekkor a szélsőségesebb értékek tudnak segíteni, pl. a int.MinValue vagy int.MaxValue, mely konstansok az int típus esetén tárolható legkisebb és legnagyobb számértéket hordozzák.
9.11. forráskód. Mátrix előfeltöltése speciális értékekkel
for (int i = 0; i < N; i++) for (int j = 0; j < M; j++) t[i] = int.MinValue;
Előfordulhat azonban olyan eset is, amikor ezek a szélsőséges értékek sem eléggé speciálisak: nem tudjuk garantálni azt sem, hogy ezek nem szerepelnek a felhasználói inputban. Ekkor a mátrixunk egyedül alkalmatlan arra, hogy megkülönböztessük vele egymástól a definiált és értékekkel nem ellátott cellákat. Készítenünk kell egy második mátrixot is, amelynek celláiban azt tároljuk el, hogy az igazi mátrixunk melyik cellája kapott már értéket, melyik nem. Erre a logikai értékek tökéletesen megfelelnek, tehát ezen másodlagos mátrixunk alaptípusa lehet bool. A bool mátrixok cellái induláskor false értékűek, ezt az értéket tekintjük a kapott-e már az igazi mátrixunk hasonló cellája értéket kiinduló értékének is (egyelőre egyik cella sem kapott). Mivel ez tökéletes induló értéknek, a bool mátrix előfeltöltésére nincs szükség, az igazi int-es mátrixunk előfeltöltésére sincs (oda is megfelelnek az induláskori 0 értékek).
9.12. forráskód. Mátrix cellája kitöltött-e
class Program { const int N = 5; const int M = 4; public static void Main() { // matrix letrehozasa int[,] m = new int[N,M]; // cellkitoltott matrix letrehozasa bool[,] b = new bool[N,M]; } }
Amikor az int-es mátrixunk valamely [i,j] cellája értéket kap, ugyanakkor a bool mátrixunk [i,j] cellájába is kell rakni true értéket, jelezvén hogy ez a cella kitöltésre került. Ezek után már a bool mátrix alapján könnyű eldönteni, hogy az eredeti mátrix mely cellája volt kitöltve, és melyik nem volt.
9.7. feladat (Feltöltés befejezése 0-val – szint: 2). A korábban megfogalmazott van még elem I/N típusú befejezéssel az a gond, hogy legalább egy mátrixbeli elemet be kell írni, hogy befejezhessük a bevitelt. A felhasználónak ehelyett úgy kell jeleznie, hogy nincs további bevitel, hogy az
vagy
koordinátához 0 értéket ír be. Amennyiben az
-hez ír be 0-t, úgy az
értékét nem kell bekérni. A bevitel végén a mátrix jelenjen meg a képernyőn táblázatos alakban!
Magyarázat: Az eddigi feladatok után nincs jelentős probléma ezzel a feladattal. A ciklus kilépését az x vizsgálata után egy break segítségével oldhatjuk meg.
while (true) { int x = int.Parse( Console.ReadLine() ); if (x==0) break; ... }
9.8. feladat (Nincs kitöltve jelzés – szint: 3). Amennyiben a felhasználó a bevitel végét kérné, a program ellenőrizze, hogy a mátrix minden eleme kitöltésre került-e! Amennyiben nem, úgy jelenítse meg a még N kitöltetlen elem van, be kívánja fejezni I/N üzenetet (N helyébe kerül a kitöltetlen elemek száma)! Amennyiben a felhasználó ennek ellenére kéri a bevitel befejezését, úgy a bevitel érjen véget! Nem válasz esetén a bevitel folytatódhasson! Ha a bevitel végét úgy kéri a felhasználó, hogy valóban már minden mátrixelem kitöltésre került, úgy a fenti üzenet ne jelenjen meg, a bevitel azonnal érjen véget! A program jelenítse meg a mátrixot a képernyőn táblázatos alakban!
Magyarázat: Alkalmazni kell valamelyik módszert annak eldönthetőségére, hogy adott mátrixelem kitöltésre került-e vagy sem. Több módszert is megemlítettünk a 2.6. feladat kapcsán. A leguniverzálisabb módszer a mátrixszal egyező méretű bool mátrix alkalmazása. A kitöltetlen elemek számát ekkor a bool mátrixban (segéd mátrix) található false értékű elemek megszámolásával tudjuk kalkulálni (lásd a 9.34. forráskód).
9.13. forráskód. Kitöltetlen elemek száma
static int kitoltetlenElemekSzama() { int db=0; for(int i=0;i<N;i++) for (int j=0;j<M;j++) if (seged[i,i]==false) db++; // return db; }
9.9. feladat (Mátrix térképe – szint: 2). Oldjuk meg a bevitelt úgy, hogy a képernyő felső részén jelenjen meg a mátrix térképe, jelezvén, hol van kitöltött és kitöltetlen elem. Ezt úgy érjük el, hogy a kitöltött elemek helyén egy * (csillag) karakter jelenjen meg, a kitöltetlen elemek helyén pedig egy . (pont) karakter. Mivel így a felhasználó folyamatosan nyomon követheti, hol van feltöltött vagy feltöltetlen elem, a kilépés kérése esetén azonnal befejezhető a bevitel.
Magyarázat: A mátrix térképét ki tudjuk rajzolni, ha alkalmazzuk például a 9.8. feladatban bemutatott logikai alapú mátrixot, melyben adminisztráljuk, melyik cella került kitöltésre, melyik nem.
9.10. feladat (Mátrix területi feltöltése – szint: 2). A mátrix feltöltése történjen billentyűzetről adatbevitellel az alábbi módon: a mátrixban elhatárolunk kis területeket, melyek a nagy mátrix kis egybefüggő téglalap alakú területei. Minden területnek van bal felső sarka (x,y) koordinátákkal megadva, szélessége és magassága. A bevitel során kérjük be egy ilyen terület bal felső sarkának koordinátáját (x,y), majd a szélességét és magasságát, végül egy feltöltő
értéket! A mátrix adott részterületébe eső cellákat töltsük fel egységesen ezen
értékkel!
Magyarázat: Ügyelni kell arra, hogy a felhasználó által megadott x,y koordináták, valamint a szel,mag szélesség, magasság értékek mellett nehogy a mátrixot alul-, vagy túlindexeljük. Ezt két módon előzhetjük meg. Az egyik módszer szerint az adatok bekérése után az értékeket „belőjük” a mátrixon belülire, levágjuk a terület esetleges kilógó részeit (9.35. forráskód). A másik módszer szerint minden egyes elemfeltöltés előtt ellenőrizzük annak sikerességét (9.36. forráskód).
9.14. forráskód. A terület elővizsgálata
int x = int.Parse(Console.ReadLine()); int y = int.Parse(Console.ReadLine()); int szel = int.Parse(Console.ReadLine()); int mag = int.Parse(Console.ReadLine()); int x = int.Parse(Console.ReadLine()); // ---- ELOZETES KORREKCIO ----- // x negativ, kilog balra if (x<0) { szel = szel+x; x=0;} // x kilog jobbra if (x>N-1) { szel=0; } // y negativ, kilog fent if (y<0) { mag = mag+y; x=0;} // y kilog lent if (y>=M) { mag=0; } // x+szel kilog jobbra if (x+szel>N-1) szel=N-x; // y+mag kilog alul if (y+mag>M-1) mag=M-y; // ----- FELTOLTES ---- for(int i=x;i<x+szel;i++) for(int j=y;j+mag;j++) m[i,j]=x;
9.15. forráskód. A koordináták egyenkénti ellenőrzése
int x = int.Parse(Console.ReadLine()); int y = int.Parse(Console.ReadLine()); int szel = int.Parse(Console.ReadLine()); int mag = int.Parse(Console.ReadLine()); int x = int.Parse(Console.ReadLine()); // ----- FELTOLTES ---- for(int i=x;i<x+szel;i++) for(int j=y;j+mag;j++) if (0<=i && i<N && 0<=j && j<M) m[i,j]=x;
A két módszer között az a különbség, hogy az első könnyebben elrontható, jól kell kalkulálni a manipulációkat, de utána gyors működésű ciklusokat kapunk. A második nehezen elrontható, de a hosszú ciklusok meneteiben minden egyes alkalommal feltételvizsgálatokat hajt végre, így a másikhoz képest mindenképpen lassabb. Ha azonban a feltöltendő területek koordinátái valóban billentyűzetes adatbevitelből származnak, akkor ez a lassúság nem észlelhető, hisz a felhasználó lassú gépelése lesz az igazi sebességbeli akadály.
9.11. feladat (Exceles bevitel – szint: 3). A mátrix elemeinek bevitelét oldjuk meg úgy, hogy induláskor megjelenítjük a képernyőn a mátrixot táblázatos alakban, a cellákban a 0 kezdőértékekkel! A felhasználónak legyen lehetősége a kurzornyilakkal kiválasztani, melyik cella értékét kívánja megadni az Enter leütésével! Ekkor a táblázat alján jelenjen meg a cella koordinátája, a cella jelenlegi értéke, és legyen lehetőség az új számérték beírására!
Magyarázat: A Console.ReadKey segítségével lehet megvalósítani a kurzorbillentyűs navigációt. Ekkor a kapott rekordban nem a .KeyChar mező az érdekes, hanem a .Key, amely egy felsorolás (enum) típusú érték. Ennek segítségével lehet eldönteni, mely vezérlőbillentyűt ütötték le (9.37. forráskód, 9.1. videó).
9.1. videó. Excel bevitel futási képernyője
9.16. forráskód. Navigálás a mátrixban
const int N = 5; const int M = 4; int[,] m = new int[N, M]; for (int k = 0; k < N; k++) { for (int l = 0; l < M; l++) Console.Write(" {0,5} ", m[k, l]); Console.WriteLine(); } int i = 0, j = 0; bool folyt = true; while (folyt) { // m[i,j] cella kiemelese Console.SetCursorPosition(j * 7, i); Console.BackgroundColor = ConsoleColor.Green; Console.Write(" {0,5} ", m[i, j]); Console.BackgroundColor = ConsoleColor.Black; // ConsoleKeyInfo k = Console.ReadKey(); // m[i,j] cella kiemeles megszuntetese Console.SetCursorPosition(j * 7,i ); Console.BackgroundColor = ConsoleColor.Black; Console.Write(" {0,5} ", m[i, j]); // .. switch (k.Key) { case ConsoleKey.UpArrow: if (i > 0) i--; break; case ConsoleKey.DownArrow: if (i < N - 1) i++; break; case ConsoleKey.LeftArrow: if (j > 0) j--; break; case ConsoleKey.RightArrow: if (j < M - 1) j++; break; case ConsoleKey.Escape: folyt = false; break; case ConsoleKey.Enter: // m[i,j] bekerese // ... break; } }
9.12. feladat (Feltöltés véletlen számokkal – szint: 1). Egy
méretű egész számokból álló mátrixot töltsünk fel véletlen számokkal, az
intervallumból! A program induláskor kérje be az N és M értékét, valamint az A és B értékeket is! A program a feltöltés után jelenítse meg a képernyőn a kapott mátrixot táblázatos alakban!
Magyarázat: Csak egyetlen fontos hibalehetőség van. Tudnunk kell, hogy egyetlen Random példány tetszőlegesen sok véletlen szám előállítására is képes. Ugyanakkor az egymáshoz időben közel (például egy ciklus belsejében történő) Random példányok ugyanazon kezdőértékről indulnak, s ugyanazon véletlen számokat fogják előállítani. Ezért ügyeljünk arra, hogy a mátrix celláinak feltöltéséhez a ciklusok előtt deklarált egyetlen Random példányt használjunk (9.38. forráskód)!
9.17. forráskód. Feltöltés véletlen számokkal
Random rnd = new Random(); // for (int i = 0; i < N; i++) { for (int j = 0; j < M; j++) m[i, j] = rnd.Next(A,B+1); }
9.13. feladat (Feltöltés fájlból – szint: 4). Egy
méretű egész számokból álló mátrixot töltsünk fel úgy, hogy a mátrixba kerülő értékek egy text fájlban vannak! A text fájl szerkezet szerinti első sora tartalmazza az N és M értékét szóközzel elválasztva, majd alatta N sor következik, mindegyikben M darab számérték, szintén szóközökkel elválasztva. A sorok végét a szokásos
\r \n
karakterek zárják. A program ügyeljen arra, hogy a text fájl hibás is lehet, vagyis egy sorban több vagy kevesebb mint M szám szerepelhet, és több vagy kevesebb mint N sora is lehet a text fájlnak! A beolvasás végén a program táblázatos formában jelenítse meg a képernyőn a beolvasott mátrixot, és jelezze, hogy tapasztalt-e hibát a text fájl feldolgozása közben!
Magyarázat: A fájlkezeléssel kapcsolatos osztályok egyrészt a System.IO, másrészt a System.Text névtérben vannak, ezért ezeket a névtereket is érdemes a kód elején a using segítségével beemelni. A fájl megnyitását a StreamReader osztály példányosításával lehet kezdeményezni. A konstruktor paramétere a fájl neve, valamint a kódlap, amellyel a fájl tartalma íródott. Az így készített r példányon keresztül lehet beolvasni egy sort a fájlból (r.ReadLine(), illetve le lehet ellenőrizni, hogy elértük-e már a fájl végét (r.EndOfStream property). A fájl adatainak kiolvasását követően a fájlt be kell zárni (r.Close() (vázlatosan a 9.39. forráskódban olvasható).
9.18. forráskód. Beolvasás fájlból
using System; using System.Text; using System.IO; string fname = @"c:\adatok.txt"; StreamReader r = new StreamReader(fname, Encoding.Default); while (!r.EndOfStream) { string s = r.ReadLine(); ... } r.Close();
A fájl egy sorának beolvasását követően a szóközzel határolt számokat tartalmazó stringet a korábban ismertetett módon a Split segítségével lehet feldarabolni, és a kinyert értékeket az int.Parse segítségével számmá alakítani, majd felhasználni (a feladatban kért hibakijelzés kezelése nélkül a 9.40. forráskódban olvasható).
9.19. forráskód. Beolvasás fájlból
StreamReader r = new StreamReader(fname, Encoding.Default); // elso sor N es M erteke string[] ee = r.ReadLine().Split(’ ’); int N = int.Parse( ee[0] ); int M = int.Parse( ee[1] ); int[,] m = new int[N,M]; // a matrix sorainak beolvasas int i=0; while (!r.EndOfStream) { string[] ss = r.ReadLine().Split(’ ’); for(int j=0;j<M;j++) m[i,j] = int.Parse(ss[j]); i++; } r.Close();
9.14. feladat (Labirintus beolvasása fájlból – szint: 3). Egy text fájlban * és . karakterekből (csillag és pont) álló sorok vannak. A sorok egyforma hosszúak, egyéb karaktert nem tartalmaznak. A * és . karakterek egy labirintust írnak le, ahol a * reprezentálja a falat, a . karakter a folyosót. Ezek együtt egy
méretű, karakterekből álló mátrixot írnak le, ahol N a sorok száma a text fájlban, M pedig a soronkénti karakterszám. A fájl első sorában egyetlen szám, a mátrix sorainak száma szerepel, a maradék sorokban a csillag és pont karakterekből álló rajzolat. Olvassuk be a mátrixot, majd jelenítsük meg a képernyőn oly módon, hogy a * karakterek pirossal, a . karakterek zöld színnel jelenjenek meg a képernyőn!
Magyarázat: A feladat olvasása során egy karakterekből álló mátrixot képzelünk el, melyben az
egyes csillag és pont karaktereket el tudjuk tárolni. Természetesen a feladat megoldható ezen az
alapon is. Képzeljük el azonban ehelyett azt, hogy egy m hosszú s string valójában egy m hosszú
karakter típusú vektor! Ekkor ha van n darab ilyen hosszú stringünk, akkor van nm
karakterünk is.
Ezért sokat egyszerűsödhet a fájlbeolvasás, ha a beolvasott stringeket nem bontjuk fel karakterekre, hanem meghagyjuk azt eredeti string alakjukban. Ez igazából a későbbi feldolgozási műveleteket nem bonyolítja (a beolvasás a 9.41 kódban látható).
9.20. forráskód. Labirintus beolvasása fájlból
StreamReader r = new StreamReader(fname, Encoding.Default); // elso sor N erteke int N = int.Parse( Console.ReadLine() ); string[] m = new string[N]; for(int i=0;i<N;i++) { m[i] = r.ReadLine(); } r.Close();
A kijelzés is könnyen megoldható, minden egyes karakter kiírása előtt át kell váltani a megfelelő írási színre (a 9.42 forráskód). Vegyük észre, hogy ha m egy string[] stringek egy vektora, akkor a m[i] az i. stringet jelöli, a m[i][j] pedig az i. string j. karakterét. Ez most nem jelölhető m[i,j] módon!
9.21. forráskód. Labirintus kiírása a képernyőre
for(int i=0;i<N;i++) { for(int j=0;j<m[i].Length;j++) { if (m[i][j]==’.’) Console.ForegroundColor = ConsoleColor.Green; else Console.ForegroundColor = ConsoleColor.Red; Console.Write(m[i][j]); } Console.WriteLine(); }
9.15. feladat (Sziget generálása – szint: 4). Az óceán közepén egy szabálytalan körvonalú sziget terül el, mely befoglalható egy
méretű téglalapba. A sziget közepén egy vulkán terül el. A téglalap minden négyzetkilométeréhez hozzárendelünk egy jellemző tengerszint feletti magasságértéket, melyet méréssel és átlagolással határoztunk meg. Generáljunk egy
méretű mátrixot, amely lehetne akár ezen sziget magassági értékeit tároló mátrixa is! A magasságértékek
közötti (méterben értett) értékek legyenek! Jelenítsük meg a mátrixot a képernyőn táblázatos alakban, használjunk színeket is a különböző magasságértékek esetén sávosan (pl:
legyen sárga,
legyen zöld, stb.)! A 9.3. ábrán látható egy minta, ahol a sötétebb színek a magasabb részeket jelölik, a világosabbak pedig az alacsonyabbakat. Ügyeljünk a következőkre:
a sziget a közepe fele haladva magasodik, tehát minél beljebb vagyunk, annál valószínűbben szerepeljenek nagyobb számértékek a mátrixban,
a sziget közepe tájékán elterülő vulkán krátere jóval alacsonyabb, mint a kráter szélei, ez egy cellát jelent a mátrix esetén (magassága [200,300] közé esik).
Magyarázat: A sziget generálását érdemes a közepén kezdeni, a vulkán kráterével.
Generáljunk oda egy kisebb értéket, a kráter közepének alacsony szintjét jelölve! A
vulkán kráterének pozícióját oly módon határozhatjuk meg, hogy meghatározzuk a
szélesség/2, magasság/2 pozíciót (a mátrix kellős közepe), majd ehhez véletlen
értéket adunk x és y irányban is. Ily módon a kiinduló pozíciónk
ugyan középre esik, de mégsem pontosan középre (tároljuk el ezt a pozíciót x0 és y0
változókba).
A következő lépés, hogy az x0 és y0 pozíciót „hízlaljuk” n vastagsággal. Ezt több módon is meg lehet tenni. Az egyik legegyszerűbb módszer, hogy szkenneljük a mátrixot soronként (és oszloponként), keresünk benne olyan cellát, amely egyelőre kitöltetlen, de a vele szomszédos cella szimpatikus. Esetünkben a szimpatikus cella az x0, y0 (8 ilyen cellát találunk, lásd a 9.4. ábra). Hogy ne kerüljön minden ilyen cella kiválasztásra, minden cella kiválasztásának esélyét csökkentsük le 100%-ról mondjuk 70%-ra! Ekkor bizonyos szomszédos cellák kiválasztásra kerülnek, mások nem (9.5. ábra).
A következő fázisban vonjuk be a szimpatikus cellák körébe az előző körben kiválasztott és átszínezett cellákat is! Igazából fogalmazhatunk úgy is, hogy minden cella szimpatikus, amely ki van töltve. Az előző módszert újra alkalmazva megint válasszuk ki a szimaptikus cellák szomszédjait 70% eséllyel! Ekkor egy vastagabb kitöltést kapunk (9.6. ábra). Jegyezzük meg, hogy a második menet kiválasztási esélyét akár csökkenthetjük is, hogy a „vastagodás” ne legyen olyan látványos! Valamint jegyezzük meg, hogy a második menetben kiválasztott cellák maximum kettő távolságra lehetnek a kezdeti pozíciótól!
A további körökben is ugyanezt fogjuk tenni, minden menetben legfeljebb egytávolságnyival „vastagítva” a kiinduló pontunkat. A nem 100% eséllyel történő kiválasztás során egy nem teljesen szabályos körvonalú befestett területet fogunk kialakítani a mátrixban. Az egyes menetekben más-más „színt” használva a kiválasztott cellák befestéséhez, kialakíthatjuk a réteges vastagítást is. A „szín” esetén itt most használhatunk más-más számintervallumot, amelyből a véletlen számokat generáljuk. A kitöltéshez használt forráskódok az 9.43 ... 9.45. forráskódokban, a futási eredmény egy lehetséges képernyőképe a 9.7. ábrán látható.
9.22. forráskód. A vastagítás kódja (1. rész)
const int N = 20; static int[,] m = new int[N, N]; static Random rnd = new Random(); // static void vulkanKratere(int a, int f) { int x = (N / 2) + rnd.Next(-1,+2); int y = (N / 2) + rnd.Next(-1, +2); m[x, y] = rnd.Next(a, f + 1); }
9.23. forráskód. A vastagítás kódja (2. rész)
static bool szomszedSzimpatikus(int x, int y) { for (int i = x - 1; i <= x + 1; i++) for (int j = y - 1; j <= y + 1; j++) if (0 <= i && i < N && 0 <= j && j < N && m[i, j] > 0) return true; // return false; } //................................................................ static void vastagit(int a, int f, int esely) { int[,] temp = (int[,])(m.Clone()); for (int i = 0; i < N; i++) for (int j = 0; j < N; j++) if (temp[i,j]==0 && szomszedSzimpatikus(i, j)) if (rnd.Next(0,100)<esely) temp[i, j] = rnd.Next(a, f + 1); m = temp; }
9.24. forráskód. A vastagítás kódja (3. rész)
static void Main() { vulkanKratere(200, 300); for(int i = 0;i<2;i++) vastagit(400, 500, 70-i*10); for (int i = 0; i < 2; i++) vastagit(300, 400, 70 - i * 10); for (int i = 0; i < 2; i++) vastagit(200, 300, 70 - i * 10); for (int i = 0; i < 2; i++) vastagit(100, 200, 70 - i * 10); // for (int i = 0; i < N; i++) { for (int j = 0; j < N; j++) { ConsoleColor c = ConsoleColor.Gray; if (m[i, j] < 100) c = ConsoleColor.Gray; else if (m[i, j] < 200) c = ConsoleColor.Yellow; else if (m[i, j] < 300) c = ConsoleColor.Green; else if (m[i, j] < 400) c = ConsoleColor.Blue; else if (m[i, j] < 500) c = ConsoleColor.Red; Console.ForegroundColor = c; if (m[i, j] == 0) Console.Write(’.’); else Console.Write(’#’); } Console.WriteLine(); } Console.ReadKey(); }
Ha alaposan megfigyeljük a futási eredményt, láthatjuk, hogy már majdnem készen vagyunk. Van azonban egy ijesztően nagy gond. A kifestett területek „belsejében” tengerszintmagasságokat észlelhetünk. Ez nem csak annak egyenes következménye, hogy a vastagítás során mind a 8 irányban ellenőrizzük a szimpatikus cella szomszédságát (9.8. ábra), ez akkor is kialakulhat, ha csak 4 irányban ellenőrizzük ezeket (9.9. ábra). A 8 irány miatt könnyű szomszédot találni, miközben a 2 cella közöttinek is van szomszédja, de az esély elvétése miatt egy cella kimarad.
Sajnos ezen egyszerűen azonban nem tudunk segíteni, mivel ez a sziget szélén normális jelenség, sőt, akkor is, ha a cella nem a szélén van, de esetleg egy szigetről a tengerbe ömlő folyó deltája, s ily módon mélyen bemetsz a sziget belsejébe (9.10. ábra).
Ebből egy fontos dolog következik. A generálás (vastagítás) közben ezek a szünetek nem szűrhetőek ki, nem korrigálhatóak, mivel akár a végső állapotban is benne maradhatnak. Helyette egy utólagos szűrési és korrigálási fázisra van szükség, hogy a belső kis tócsákat megszüntessük. Persze, ha megengedett a sziget belsejében a tengerszint magasságában lévő területek jelenléte (beszüremlő tengervízi tavak, alacsonyan fekvő lapos földrészek, stb.), akkor ezen korrigálási lépésre nincs is szükség.
Tegyük fel, hogy nem megengedett! Hogyan különítsük el a szigetet körbefonó víz 0 magasságú celláit a hibásnak számító belső 0 értékű celláktól? A kulcs: a belső részek körbe vannak zárva magasabb részekkel. Itt megint lehet vitázni, hogy a bezárást mind a 8 irányból igényeljük-e, vagy 4 irányú bezárást már elégnek tekintünk. De ez csak a detektáló algoritmus apró módosításán múlik. A feladat egyelőre úgy fogalmazható meg: különítsük el a belső, bezárt 0 magasságú cellákat a külső (normális) 0 magasságú celláktól.
A megoldást a festő algoritmus jelentheti. Feltételezhetjük, hogy a 0,0 pozíción tengervíz van
(ha ezt nem feltételezhetjük, akkor keresnünk kell egy szélső cellát, ahol 0 magasság van – az
biztosan tengervíznek tekinthető). Ha ilyet nem találunk, akkor készen is vagyunk: minden 0
magasságú cella biztosan hibás! Ezt tehát egyelőre zárjuk ki, legyen egy kiinduló
,
cellánk, ahol biztosan tengervíz van. Ezen cellából kiindulva fessük
be a tenger vizét (4 vagy 8 irányban lépkedve, a korábban említettektől függően)!
Ezek után amely 0 magasságú cellához nem jutott el a festés, az „belső cella”, vagyis
hibásnak tekinthető a benne szereplő 0 érték. A 9.11. ábrán látható a 4
irányú festés eredménye, a tengervizet sötétkék színű pöttyök szimbolizálják, míg a
belső, „hibás” cellák maradtak pont karakterek. A festő algoritmus a 9.46.
forráskódban olvasható, indítása a külső jolBefest(); függvény hívásával történik
meg. Ez minden szélső cellában talált 0 (tengervíz) cellából kiindulva elvégzi a
festést.
9.25. forráskód. Festő algoritmus
static void befest(int x, int y) { if (m[x, y] != 0) return; m[x, y] = 1; if (x > 0) befest(x - 1, y); if (x <N-1) befest(x + 1, y); if (y > 0) befest(x, y-1); if (y < N - 1) befest(x, y+1); } //................................................................ static void jolBefest() { for (int i = 0; i < N; i++) { if (m[i, 0] == 0) befest(i, 0); if (m[0, i] == 0) befest(0, i); if (m[i, N-1] == 0) befest(i,N-1); if (m[N - 1,i] == 0) befest(N - 1,i); } }
Hogyan korrigáljuk utólag a 0 magasságú hibás cellát? Vegyük a szomszédait, amelyek nem 0
értékűek, és állítsuk be a hibás cella magasságát egy átlagértékre (esetleg kis véletlen
értéket hozzáadva, mondjuk 20 értékben, persze ügyelve, nehogy negatívba
csússzunk át, vagy átlépjük az 500-as maximumot! A korrekciót végző függvények (a festés
követően indíthatóak) a 9.47. forráskódban olvashatóak, a végeredmény a
9.12. ábrán látható.
9.26. forráskód. Korrekciózás a belső cellákra
static int atlagSzamit(int x, int y) { int ossz = 0, db = 0; for (int i = x - 1; i <= x + 1; i++) for (int j = y - 1; j <= y + 1; j++) if (0 <= i && i < N && 0 <= j && j < N && m[i, j] > 0) { ossz += m[i]; db++; } if (db == 0) return 0; else return ossz/db; } //................................................................ static void korrekcio() { for (int i = 0; i < N; i++) for (int j = 0; j < N; j++) if (m[i, j] == 0) { int x = atlagSzamit(i, j)+rnd.Next(-20,20); if (x<0) x=0; else if (x>500) x=500; m[i, j] = x; } }
9.16. feladat (Aknakereső – szint: 2). Az ismert aknakereső játékban egy
méretű mátrixba kell K darab aknát elhelyezni. Az aknák elhelyezésének szabályai:
ne tegyünk kétszer ugyanarra a helyre aknát, hogy a K darab akna a területen fellelhető legyen K különböző cellában,
az aknák oldalukkal, sarkukkal szomszédos cellákba is kerülhetnek,
az aknák a mátrix szélső celláiba is kerülhetnek.
A megfelelően feltöltött mátrixot jelenítsük meg a képernyőn oly módon, hogy az üres cellákat zöld színű . (pont) karakterek szimbolizálják, míg az aknát tartalmazó cellákat piros színű * (csillag) karakterek jelezzék!
Magyarázat: Ez egy rendkívül könnyű feladat, mivel csak arra kell ügyelni, hogy ha olyan x, y koordinátát generálnánk, ahova már helyeztünk aknát korábban, akkor új koordinátapárt kell generálnunk. Akkor végeztünk, ha K üres cella koordinátáját sikerült generálnunk. A kiírás az előző feladatban leírtak szerint szintén egyszerű művelet.
Sokkal izgalmasabb feladat az aknakereső játék azon része, amikor a kész, aknákkal teli mátrix valamely x, y koordinátájú pozícióját a játékos kiválasztja. Ha ott akna van – a játék véget ér. Ha nincs ott akna, akkor ezen koordinátából induló befestéssel meghatározhatjuk, mely terület tárul fel a játéktérből. A festő algoritmus szintén az előző feladat megoldása során került bemutatásra.
9.17. feladat (Torpedó játék – szint: 5). Egy
méretű területen hajókat helyezünk el. A hajók mérete és száma különböző:
csónakok (egyetlen cellát foglalnak el),
halászhajók (két szomszédos cellát foglalnak el),
cirkálók (három cella méretűek),
rombolók (négy cella méretűek),
anyahajók (öt cella méretűek).
A hajók alakja tetszőleges lehet, feltéve ha összefüggőek. Összefüggő akkor egy hajó, ha a hajót alkotó minden cella legalább egy másik cellával él mentén érintkezik.
Készítsünk olyan programot, amely egy
méretű mátrixban elkészíti a hajók egy véletlenszerű elhelyezését oly módon, hogy 1 db anyahajó, 2 db hadihajó, 3 db cirkáló, 4 halászhajó és 5 csónak kerül elhelyezésre! A hajók egymással nem érintkezhetnek, még a sarkaik mentén sem, vagyis két különböző hajót alkotó cella még sarkával sem érhet össze. A hajók viszont a terület széleit elérhetik.
A programot úgy kell elkészíteni, hogy rugalmasan alkalmazkodjon a hajók darabszámának változásához, vagyis a hajók számát egy konstans formájában adjuk meg. A konstans értékének átírásakor a program megfelelő számú hajót helyezzen el! A program ügyeljen arra, hogy nagy mennyiségű hajó egyszerűen nem helyezhető el a területen (mivel a hajók nem érhetnek össze)! A program ilyen esetben se kerüljön végtelen ciklusba, jelezze ki a megoldhatatlanságot, és álljon le a futása!
A program a sikeres elhelyezés végén jelenítse meg a hajókat a képernyőn, * karakterrel jelezve a hajót felépítő cellákat, és . karakterrel a vizet!
Magyarázat: Ez egy nagyon nehéz feladat, ha jó minőségű megoldást akarunk készíteni. Először is vegyük a hajók alakját! A 9.14. ábrán látható alakú hajók megjelenése (is) kívánatos. Ez azért fontos, mert az első „nehéz” dolog olyan algoritmust írni, amely ilyen alakú hajókat is képes generálni. Egyszerűbb olyanban gondolkodni, ahol egy kiinduló x,y koordinátából lineárisan növesztjük a hajó alakját (vagyis minden új cellát az előző cellához kapcsolunk a 4 irány egyikében). Ekkor például az A, B, E alakú hajók nem kerülnének generálásra.
Egyébként is fontos a tetszőleges alakú hajók generálásának képessége, mivel egy rossz kezdőpozícióból rossz irányban indított hajógenerálás akár zsákutcába is vezethetne, ami a jó minőségű megoldásba nem fér bele. A 9.15. ábrán látható szituációban a kezdőpozíciónk (1) egy olyan területre mutat, amelyet a korábban generált hajók már erősen körülzártak. A felfele indulás után a (3)-as pozíciótól kezdve a lineráris generálás zsákutcába jutna, míg az ügyesebb generáló algoritmus képes lenne befejezni az 5-ös méretű anyahajó generálását ezek után is (az ábrán a sötétszürke mezők a hajók, a keresztben áthúzott cellák a hajók környezetét jelölik).
A tetszőleges alakú hajó generálása működhet úgy is, hogy a hajó elemeinek generálása során egy listába gyűjtjük az elemeinek koordinátáit (kezdetben a legelső eleme kerül bele, melynek pozíciókiválasztásáról később lesz szó). Amikor új elemet kívánunk még a hajóhoz csatolni, akkor ezen listából véletlenszerűen kiválasztunk egy már létező cellát, és egy véletlenszerű folytatási irányt (észak/dél/kelet/nyugat), majd megpróbáljuk ebbe az irányba folytatni a hajót. Ha nem sikerül, akkor választhatunk másik létező cellát és/vagy másik irányt. Ha már minden lerakott cellát és a belőlük kiinduló minden irányt kipróbálta az algoritmus – akkor beláthatjuk, hogy az adott kezdőpozícióból kiindulva nem lehet ezt a (nagyméretű) hajót lerakni.
Ehhez támogatásképpen kihasználhatjuk azt, hogy a listából törölni is lehet elemet. Tegyük fel, hogy már három cellát leraktunk a generálandó 5 méretű anyahajóból. A lista ekkor mindhárom cella mind a négy irányú folytatásának koordinátáit tartalmazza, 12 elemet. Véletlenszerűen választunk a 12 elemből, és ellenőrizzük a célt, hogy oda valóban letehetjük-e a folytatást. Ha sikerül, akkor 4 cellánk van, és újrageneráljuk a listát immár 16 elemmel. Ha nem sikerül, akkor a hibás próbálkozás koordinátáit töröljük a listából (1-gyel kevesebb elem marad). Ha a lista elfogy, akkor a hajót nem lehet folytatni, a kiindulási pontból a hajót nem lehet befejezni.
Ha egy hajót a mátrix egyetlen kezdőpozíciójából sem lehet már lerakni, akkor nagy a baj. Nem feltétlenül végzetes, de nagy. Ekkor az utolsó sikeresen lerakott hajót törölnünk kell, majd megpróbálni lerakni máshova vagy más alakkal, és újra kísérletet tenni a következő hajó lerakására is.
Érezhető, hogy itt a visszalépéses keresésre (backtrack) lesz szükség. A hajók lerakásának
kezdőpozícióját véletlenszerűen bár, de szervezett sorrendben kell kiválasztani. A visszalépés
során ugyanis másik pozíciót kell keresni, olyat, amely még nem került kiválasztásra.
Ehhez a véletlen x,y koordinátaválasztás nem eléggé kifinomult. Helyette javasolt az
koordináta listába szervezése, majd véletlen összekeverése. A listát sorban
haladva dolgozhatjuk fel, a kapott koordináták mégis véletlenszerű sorrendet produkálnak. Így
megoldhatjuk, hogy a visszalépés során új kezdőpozíciót választhassunk a hajóknak,
módszeresen kipróbálhassuk az összes kezdőpozíciót a véletlenszerűség megőrzése
mellett.
A következő probléma az adott pozícióból kiinduló összes alakzat generálása. Nem arról van most szó, hogy ha nem rakható le a hajó, akkor bizonyosodjunk meg róla, hanem arról, hogy adott pozícióból kiindulva többféle alakban is lerakható.
Elképzelhető olyan szituáció, hogy az N. hajó lerakása adott kezdőpozícióból lehetetlen,
de ha visszalépünk az . (sikeresen lerakott) hajóhoz, és őt egy másik alakzat
formájában rakjuk le, akkor az N. hajót a korábban sikertelen kezdőpozícióról egy újabb
próbálkozás során már sikeresen le lehet rakni.
Az első probléma ez ügyben: hogyan tudunk módszeresen, de véletlenszerűen építkezni a már meglévő cellákból kiindulva? Az n cellából álló hajónk első lépésben csak 1 cellából áll, a kezdőpozíción. Ha sikerül még egy cellát leraknunk a környezetében, akkor már két cellánk lesz, és a harmadik cella helyének keresésekor kiindulhatunk az elsőből és a másodikból is. Készítsünk egy tervet előre! Generáljuk le a 9.16. ábrán látható háromszögmátrixot, soronként permutálva a szabályosan felépített bal oldali tervet! Ennek megfelelően a második cella generálásához az 1-es cellát vesszük kiindulási alapként (első sor, mondjuk itt más lehetőség nincs is), a harmadik cella helyét először a 2-esből, ha onnan nem járunk sikerrel, akkor az 1-esből kezdjük el (második sor). A negyedik cella helyének generálását elsőként a 3-asból, ha onnan nem megy, akkor az 1-esből, ha onnan sem, akkor a 2-esből fogjuk megpróbálni (3-as sor), stb.
Ha megvan, melyik cellából indulunk ki, akkor az elhelyezést kell véletlenszerű, de módszeres módon megoldani. Mivel csak 4 irányba tudunk szomszédos cellát választani a fejlesztés során, jelöljük el ezeket É, D, K, NY módon (vagy 0, 1, 2, 3 számokkal)! Egy ötcellás hajó esetén négyszer kell továbblépési irányt választani valamely már elhelyezett cellából, ezért készítsük el a 9.17. ábrán látható iránymátrixot először szabályosan felépítve, majd soronként, cserék segítségével permutálva. Ekkor például a harmadik cella helyét úgy határozzuk meg, hogy az előzőekben kiválasztott, már lerakott cellából (első vagy második cella) először nyugatra próbálkozunk. Ha oda nem lehet valamiért, akkor dél, majd észak, végül kelet felé próbáljuk meg (az iránymátrix második sora). Ha egyik sem válik be, akkor másik cellából próbálkozunk a továbbfejlesztéssel. Ha semelyik (sem az első, sem a második) korábban lerakott cellából nem lehet a harmadik cellát lerakni, akkor felszedjük a második cellát, és azt próbáljuk meg új helyre lerakni.
A visszalépéses keresés révén ekkor a hajó következő lerakandó celláját véletlenszerű irányokban, a következő cellát véletlenszerű (korábban már lerakott) cellából kiindulva próbáljuk meg lerakni. Ha sikerül, akkor lépünk egyet előre, és megpróbáljuk a hajó újabb celláját lerakni. Ha sikerül, akkor a hajó teljes egészében felépül. Ha valamely lerakási mintából – melynek során m cellát már sikeresen letettünk – egyáltalán nem sikerül a hajó újabb cellájának elhelyezése, akkor visszalépünk m – 1 sikeres lerakásig, és az m. cellát máshova próbáljuk lerakni. Ha a visszalépés során az 1. cellát is fel kell szednünk, akkor a hajó lerakása adott kezdőpozíciótól lehetetlen, semmilyen alakban sem kivitelezhető. Ekkor visszalépünk az előző sikeresen lerakott hajóhoz, és ezt próbáljuk meg más alakban, de egyelőre ugyanazon kezdőpozícióból letenni. Ha ezt a hajót ebből a kezdőpozícióból már semmilyen más alakban sem tudjuk lerakni, akkor visszalépünk az őt megelőző hajóhoz. Ha a visszalépés során az első hajót kell más pozícióba rakni, akkor azt is meg kell tennünk. Ha az első hajót már minden létező pozícióból megpróbáltuk letenni, de a továbbiakban sosem sikerült az összes hajót lerakni, akkor a hajók elhelyezése az adott méretű pályán lehetetlen.
9.27. forráskód. Adatbekérés koordinátákkal
int db=0; while (db<N*M) { Console.Write("Kerem a matrix sorat [1..{0}]:",N); int i = int.Parse( Console.ReadLine() ); Console.Write("Kerem a matrix oszlopat [1..{0}]:",M); int j = int.Parse( Console.ReadLine() ); Console.Write("Kerem az erteket:"); int x = int.Parse( Console.ReadLine() ); if (1<=i && i<=N && 1<=j && j<=M) { m[i-1,j-1] = x; db++; } else Console.Write("Na ezt megegyszer, hibas volt."); }
9.28. forráskód. Adatbekérés koordinátákkal v2.0
for (int db=0;db<<N*M;db++) { // sor int i; do { Console.Write("Kerem a matrix sorat [1..{0}]:",N); i = int.Parse( Console.ReadLine() ); } while (i<1 || i>N); // oszlop int j; do { Console.Write("Kerem a matrix oszlopat [1..{0}]:",M); j = int.Parse( Console.ReadLine() ); } while (j<1 || j>M); // ertek Console.Write("Kerem az erteket:"); int x = int.Parse( Console.ReadLine() ); // tarolas m[i-1,j-1] = x; }
9.29. forráskód. Adatbekérés függvények segítségével v3.0
static int sorBekeres() { int i; Console.Write("Kerem a matrix sorat [1..{0}]:",N); do { i = int.Parse( Console.ReadLine() ); if (i<1 || i>N) Console.WriteLine("Nem jo. Ujra!"); } while (i<1 || i>N); return i; } //................................................................ static int oszlopBekeres() { Console.Write("Kerem a matrix oszlopat [1..{0}]:",M); while(true) { int j = int.Parse( Console.ReadLine() ); if (1<=j && j<=M) return j; else Console.WriteLine("Nem jo. Ujra!"); } } //................................................................ static void Main() { ... for (int db=0;db<<N*M;db++) { int i=sorBekeres(); int j=oszlopBekeres(); Console.Write("Kerem az erteket:"); int x = int.Parse( Console.ReadLine() ); // tarolas m[i-1,j-1] = x; } }
9.30. forráskód. String felbontása a Split segítségével
// pl "12,24,35" string s = Console.ReadLine(); string[] elemek = s.Split(’,’); // ez esetben az elemek vektor 3 elemű lesz // elemek[0] = "12" // elemek[1] = "24" // elemek[2] = "35"
9.31. forráskód. Console.ReadKey használata
ConsoleKeyInfo k = Console.ReadKey(); if (k.KeyChar == ’n’ || k.KeyChar == ’N’) Console.Write("NEM"); else if (k.KeyChar == ’i’ || k.KeyChar == ’I’) Console.Write("IGEN");
9.32. forráskód. Mátrix előfeltöltése speciális értékekkel
for (int i = 0; i < N; i++) for (int j = 0; j < M; j++) t[i] = int.MinValue;
9.33. forráskód. Mátrix cellája kitöltött-e
class Program { const int N = 5; const int M = 4; public static void Main() { // matrix letrehozasa int[,] m = new int[N,M]; // cellkitoltott matrix letrehozasa bool[,] b = new bool[N,M]; } }
9.34. forráskód. Kitöltetlen elemek száma
static int kitoltetlenElemekSzama() { int db=0; for(int i=0;i<N;i++) for (int j=0;j<M;j++) if (seged[i,i]==false) db++; // return db; }
9.35. forráskód. A terület elővizsgálata
int x = int.Parse(Console.ReadLine()); int y = int.Parse(Console.ReadLine()); int szel = int.Parse(Console.ReadLine()); int mag = int.Parse(Console.ReadLine()); int x = int.Parse(Console.ReadLine()); // ---- ELOZETES KORREKCIO ----- // x negativ, kilog balra if (x<0) { szel = szel+x; x=0;} // x kilog jobbra if (x>N-1) { szel=0; } // y negativ, kilog fent if (y<0) { mag = mag+y; x=0;} // y kilog lent if (y>=M) { mag=0; } // x+szel kilog jobbra if (x+szel>N-1) szel=N-x; // y+mag kilog alul if (y+mag>M-1) mag=M-y; // ----- FELTOLTES ---- for(int i=x;i<x+szel;i++) for(int j=y;j+mag;j++) m[i,j]=x;
9.36. forráskód. A koordináták egyenkénti ellenőrzése
int x = int.Parse(Console.ReadLine()); int y = int.Parse(Console.ReadLine()); int szel = int.Parse(Console.ReadLine()); int mag = int.Parse(Console.ReadLine()); int x = int.Parse(Console.ReadLine()); // ----- FELTOLTES ---- for(int i=x;i<x+szel;i++) for(int j=y;j+mag;j++) if (0<=i && i<N && 0<=j && j<M) m[i,j]=x;
9.37. forráskód. Navigálás a mátrixban
const int N = 5; const int M = 4; int[,] m = new int[N, M]; for (int k = 0; k < N; k++) { for (int l = 0; l < M; l++) Console.Write(" {0,5} ", m[k, l]); Console.WriteLine(); } int i = 0, j = 0; bool folyt = true; while (folyt) { // m[i,j] cella kiemelese Console.SetCursorPosition(j * 7, i); Console.BackgroundColor = ConsoleColor.Green; Console.Write(" {0,5} ", m[i, j]); Console.BackgroundColor = ConsoleColor.Black; // ConsoleKeyInfo k = Console.ReadKey(); // m[i,j] cella kiemeles megszuntetese Console.SetCursorPosition(j * 7,i ); Console.BackgroundColor = ConsoleColor.Black; Console.Write(" {0,5} ", m[i, j]); // .. switch (k.Key) { case ConsoleKey.UpArrow: if (i > 0) i--; break; case ConsoleKey.DownArrow: if (i < N - 1) i++; break; case ConsoleKey.LeftArrow: if (j > 0) j--; break; case ConsoleKey.RightArrow: if (j < M - 1) j++; break; case ConsoleKey.Escape: folyt = false; break; case ConsoleKey.Enter: // m[i,j] bekerese // ... break; } }
9.38. forráskód. Feltöltés véletlen számokkal
Random rnd = new Random(); // for (int i = 0; i < N; i++) { for (int j = 0; j < M; j++) m[i, j] = rnd.Next(A,B+1); }
9.39. forráskód. Beolvasás fájlból
using System; using System.Text; using System.IO; string fname = @"c:\adatok.txt"; StreamReader r = new StreamReader(fname, Encoding.Default); while (!r.EndOfStream) { string s = r.ReadLine(); ... } r.Close();
9.40. forráskód. Beolvasás fájlból
StreamReader r = new StreamReader(fname, Encoding.Default); // elso sor N es M erteke string[] ee = r.ReadLine().Split(’ ’); int N = int.Parse( ee[0] ); int M = int.Parse( ee[1] ); int[,] m = new int[N,M]; // a matrix sorainak beolvasas int i=0; while (!r.EndOfStream) { string[] ss = r.ReadLine().Split(’ ’); for(int j=0;j<M;j++) m[i,j] = int.Parse(ss[j]); i++; } r.Close();
9.41. forráskód. Labirintus beolvasása fájlból
StreamReader r = new StreamReader(fname, Encoding.Default); // elso sor N erteke int N = int.Parse( Console.ReadLine() ); string[] m = new string[N]; for(int i=0;i<N;i++) { m[i] = r.ReadLine(); } r.Close();
9.42. forráskód. Labirintus kiírása a képernyőre
for(int i=0;i<N;i++) { for(int j=0;j<m[i].Length;j++) { if (m[i][j]==’.’) Console.ForegroundColor = ConsoleColor.Green; else Console.ForegroundColor = ConsoleColor.Red; Console.Write(m[i][j]); } Console.WriteLine(); }
9.43. forráskód. A vastagítás kódja (1. rész)
const int N = 20; static int[,] m = new int[N, N]; static Random rnd = new Random(); // static void vulkanKratere(int a, int f) { int x = (N / 2) + rnd.Next(-1,+2); int y = (N / 2) + rnd.Next(-1, +2); m[x, y] = rnd.Next(a, f + 1); }
9.44. forráskód. A vastagítás kódja (2. rész)
static bool szomszedSzimpatikus(int x, int y) { for (int i = x - 1; i <= x + 1; i++) for (int j = y - 1; j <= y + 1; j++) if (0 <= i && i < N && 0 <= j && j < N && m[i, j] > 0) return true; // return false; } //................................................................ static void vastagit(int a, int f, int esely) { int[,] temp = (int[,])(m.Clone()); for (int i = 0; i < N; i++) for (int j = 0; j < N; j++) if (temp[i,j]==0 && szomszedSzimpatikus(i, j)) if (rnd.Next(0,100)<esely) temp[i, j] = rnd.Next(a, f + 1); m = temp; }
9.45. forráskód. A vastagítás kódja (3. rész)
static void Main() { vulkanKratere(200, 300); for(int i = 0;i<2;i++) vastagit(400, 500, 70-i*10); for (int i = 0; i < 2; i++) vastagit(300, 400, 70 - i * 10); for (int i = 0; i < 2; i++) vastagit(200, 300, 70 - i * 10); for (int i = 0; i < 2; i++) vastagit(100, 200, 70 - i * 10); // for (int i = 0; i < N; i++) { for (int j = 0; j < N; j++) { ConsoleColor c = ConsoleColor.Gray; if (m[i, j] < 100) c = ConsoleColor.Gray; else if (m[i, j] < 200) c = ConsoleColor.Yellow; else if (m[i, j] < 300) c = ConsoleColor.Green; else if (m[i, j] < 400) c = ConsoleColor.Blue; else if (m[i, j] < 500) c = ConsoleColor.Red; Console.ForegroundColor = c; if (m[i, j] == 0) Console.Write(’.’); else Console.Write(’#’); } Console.WriteLine(); } Console.ReadKey(); }
9.46. forráskód. Festő algoritmus
static void befest(int x, int y) { if (m[x, y] != 0) return; m[x, y] = 1; if (x > 0) befest(x - 1, y); if (x <N-1) befest(x + 1, y); if (y > 0) befest(x, y-1); if (y < N - 1) befest(x, y+1); } //................................................................ static void jolBefest() { for (int i = 0; i < N; i++) { if (m[i, 0] == 0) befest(i, 0); if (m[0, i] == 0) befest(0, i); if (m[i, N-1] == 0) befest(i,N-1); if (m[N - 1,i] == 0) befest(N - 1,i); } }
9.47. forráskód. Korrekciózás a belső cellákra
static int atlagSzamit(int x, int y) { int ossz = 0, db = 0; for (int i = x - 1; i <= x + 1; i++) for (int j = y - 1; j <= y + 1; j++) if (0 <= i && i < N && 0 <= j && j < N && m[i, j] > 0) { ossz += m[i]; db++; } if (db == 0) return 0; else return ossz/db; } //................................................................ static void korrekcio() { for (int i = 0; i < N; i++) for (int j = 0; j < N; j++) if (m[i, j] == 0) { int x = atlagSzamit(i, j)+rnd.Next(-20,20); if (x<0) x=0; else if (x>500) x=500; m[i, j] = x; } }