Utolsó frissítés: 2012.02.13.
Facebook Twitter iWiW MySpace Digg Delicious Google bookmarks Startlap Windows Live

Tömbök II.

Asszociatív tömbök

Az előző leckében olyan tömbökkel foglalkoztunk, melyek például listák megvalósítására alkalmasak. A lista alatt egy olyan adatszerkezetet kell elképzelni, melynél valamilyen adatok (a tömb elemei) vannak sorbarakva, és minden elemnek van egy sorszáma (index). A PHP-ben a tömbök segítségével ennél általánosabb adatszerkezeteket is létrehozhatunk. Tegyük fel, hogy csinálunk az oldalunkra valamilyen regisztrációt, és egy felhasználó adatait egy tömbben szeretnénk tárolni. Legyen a felhasználónak egy azonosítója (sorszáma), neve, jelszava, e-mail címe és életkora. Az azonosító és az életkor egész szám, a többi karakterlánc. Ez nem egy lista, hanem egy halmaz, ahol lényegtelen az elemek sorrendje. A halmaz elemeire azonban itt is hivatkoznunk kell valahogy. Ezt megtehetnénk az előző leckében látott módon is:

$admin = array(1, "Hörb", "xxx", "example@example.com", 23);

Az $admin tömbbe beraktuk a Hörb nevű felhasználó adatait. Így ha Hörb e-mail címére vagyunk kíváncsiak, azt az $admin[3] hivatkozással tudjuk elérni. Sokkal szerencsésebb lenne, ha a hivatkozásra ránézve látnánk, hogy miről van szó, de ebben az esetben csak egy 3-as számot látunk, amiből nehéz kitalálni, hogy ez az e-mail cím akar lenni. A másik probléma, hogy ha bővítjük a halmazt úgy, hogy az új elemet az e-mail cím elé rakjuk, máris írkálhatjuk át az összes hivatkozást. A PHP ezért lehetőséget biztosít olyan tömbök létrehozására, melynek elemeire egész számok helyett karakterláncokkal hivatkozhatunk. Így az $admin tömböt az alábbi módon is feltölthetjük:

	$admin["id"] = 1;
	$admin["nev"] = "Hörb";
	$admin["jelszo"] = "xxx";
	$admin["email"] = "example@example.com";
	$admin["kor"] = 23;

Ezután ugyanígy hivatkozhatunk az egyes elemekre, vagyis az e-mail címre például $admin["email"] módon, amin látszik, hogy mit tartalmaz az adott elem. Ez már valóban egy halmaz lett, mivel az elemek közt nincs sorrend. A hivatkozásokat sem kell megváltoztatni, ha egy új elemet szúrunk be a halmazba.
Az ilyen, karakterláncokkal indexelt tömböket asszociatív tömböknek nevezzük. Ebben az esetben a szögletes zárójelek közötti karakterláncot általában nem indexnek, hanem kulcsnak hívjuk. Egy asszociatív tömböt kulcs-érték párok halmazaként érdemes elképzelni:

Asszociatív tömb

Persze a kulcsokban használhatnánk ékezetes betűket is, mivel ezek nem változónevek, hanem karakterláncok. Valójában a számokkal indexelt tömbök is asszociatív tömbök, csak ott a kulcsok nem karakterláncok, hanem egész számok. A továbbiakban a karakterláncokkal "indexelt" tömböket asszociatív tömböknek, a többit (egész számmal indexelt tömböket) csak simán tömböknek nevezzük.
Az array() függvényt használhatjuk asszociatív tömbök esetén is, olyan módon, hogy a paramétereknek kulcs => érték alakú kifejezéseknek kell lenniük. Például:

$admin = array("id" => 1, "nev" => "Hörb", "jelszo" => "xxx", "kor" => 23);

Ez az értékadási forma egyenértékű a fentivel. Az => egy operátor, ami csak tömbök esetén használható, a bal oldalán egy kulcs áll (ami egy nemnegatív egész szám vagy egy karakterlánc), a jobb oldalán pedig az érték, amit a kulcshoz rendelünk. Az érték bármilyen típusú változó lehet. Ez nem a nagyobb vagy egyenlő operátor, mivel ott >= sorrendben szerepelnek a jelek, ez inkább egy nyilat akar szimbolizálni.
Számokkal indexelt tömböknél is alkalmazható ez a forma, mivel mint említettem, azok is asszociatív tömbök, ahol a kulcsok egész számok:

$prim = array(1 => 2, 2 => 3, 3 => 5, 4 => 7);

Ezzel egy 1-től indexelt tömböt hoztunk létre. Amikor nem adjuk meg a kulcsokat az array() függvénynek, az automatikusan hozza létre őket. Az első elemhez (értékhez) a 0 egész számot (kulcsot) rendeli, amennyiben nem adtuk meg, a többi elem a megelőző elem kulcsánál (ha az egy egész szám) eggyel nagyobb számot kap. Például:

$honap = array(1 => "január", "február", "március", ..., "december");

Ebben az esetben a "február" érték automatikusan a 2-es indexet kapja, és így tovább. Például a $honap[3] értéke "március" lesz. Asszociatív tömbök értékadása esetén sokszor áttekinthetőbb a kód, ha a kulcs-érték párokat egymás alá írjuk:

	$nap = array(
		"h"   => "hétfő",
		"k"   => "kedd",
		"sze" => "szerda",
		...
		"v"   => "vasárnap"
	);

Tömbök bejárása

Egy tömb bejárása azt jelenti, hogy végigmegyünk annak összes elemén. Olyan tömbökön, melyeknek indexei egész számok és ismert az első érték, könnyen végighaladhatunk egy ciklussal, ahol a ciklusváltozót használhatjuk a tömb indexelésére (és az egyes elemek elérésére). A tömb végének megállapítására a házi feladatban az isset() függvényt használtuk, de jobb módszer a count() függvény használata.
A count() egy tömböt vár paraméterként, és visszatérési értéke a tömb elemeinek száma. Így az első házi feladat egy másik megoldása:

	for ($n = 0; $n < count($gyumolcs); ++$n){
		print $gyumolcs[$n].", ";
	}

A count() visszatérési értéke a tömb elemeinek száma, ami független az indexeléstől, így egy 0-tól indexelt tömb esetén az utolsó elem indexe count(tömb)-1. A count() olyan szempontból jobb megoldás, mint az isset() használata, hogy gyorsabb program írható vele. Mivel egy függvényhívás hosszabb ideig tart, mint egy változó kiértékelése, ezért érdemes a ciklusfeltételben függvényhívások helyett változókat használni, amennyiben ez lehetséges. Persze a fenti program nem éppen a legjobb példa, mivel itt pont egy függvényhívás szerepel a ciklusfeltételben, így a count($gyumolcs) függvényhívás annyiszor lefut, amennyi a tömb elemeinek száma. Mivel a tömbelemek száma a ciklus futása során nem változik, megtehetjük azt is, hogy a ciklus előtt eltároljuk a count($gyumolcs) kifejezés értékét egy változóban és ezt a változót használjuk a ciklusfeltételben:

	$num = count($gyumolcs);
	for ($n = 0; $n < $num; ++$n){
		print $gyumolcs[$n].", ";
	}

Így gyorsabb a program, és elvileg hamarabb töltődik be az oldalunk. Ez mind szép és jó, de hogy tudunk bejárni egy asszociatív tömböt? Mivel ott az indexek (pontosabban kulcsok) karakterláncok, nem használhatjuk a ciklusváltozót az indexelésre, és lehetőleg egy olyan módszer kellene, ahol nem kell ismerni a kulcsok neveit. Kifejezetten asszociatív tömbök bejárására való a foreach szerkezet. Ezt a szerkezetet felfoghatjuk úgy, mint egy negyedik típusú ciklust (ugyebár az első három típus a while-, for- és do-ciklus). A foreach-szerkezet általános alakja:

	foreach (tömbnév as kulcs => érték){
		utasítások
	}

Itt a foreach és as kulcsszavak, a tömbnév helyére kerül a tömb neve, amit be akarunk járni, a kulcs és az érték helyére pedig tetszőleges változóneveket írhatunk. Mivel ez tulajdonképpen hasonlóan viselkedik, mint egy ciklus, az utasítások részt nevezzük el ciklusmagnak. A kulcs és érték helyére írt változókat a ciklusmagon belül úgy használhatjuk, mint például egy függvény törzsében a paraméterlistában megadott változókat. A szerkezet azt csinálja, hogy végigmegy a tömb változón, és minden egyes elemére elvégzi a ciklusmag utasításait, vagyis ugyanúgy használható, mint a fentebb látott ciklus. A ciklusmag tehát annyiszor fog lefutni, ahány eleme van a tömbnek. Például így írathatjuk ki egy asszociatív tömb minden elemét:

	foreach ($admin as $kulcs => $ertek){
		print $kulcs.": ".$ertek."<br />";
	}

Ez az alábbi kimenetet hozza létre:
id: 1
nev: Hörb
jelszo: xxx
email: norbqcd,gmail,com
kor: 23

Amikor belépünk a szerkezetbe, az $admin tömb első elemének kulcsa bekerül a $kulcs változóba, az értéke pedig az $ertek-be. Ezt a két változót használhatjuk a ciklusmagon belül. Amikor lefutott a ciklusmag, jön a tömb következő eleme. Ha már a tömb minden elemére lefutott a mag, a szerkezetből kiugrik a program, a vezérléssel tehát egyáltalán nem kell foglalkoznunk. Az, hogy milyen sorrendben kerülnek sorra a tömb elemei, a tömb létrehozásától függ. Az asszociatív tömbök esetében is van egy meghatározott sorrend, ami megegyezik az elemek létrehozásának a sorrendjével. Ha például egy tömböt így hozunk létre:

$tomb[1] = 1; $tomb[0] = 0; $tomb[3] = 3; $tomb[2] = 2;

akkor a foreach szerkezetet ráengedve, az 1, 0, 3, 2 sorrendben fogja kiírni a számokat (illetve végrehajtani a műveleteket), vagyis a sorrend független a kulcsok értékeitől.
A foreach szerkezetet az alábbi, egyszerűbb formában is használhatjuk, ha nincs szükségünk az elemek kulcsára:

	foreach (tömbnév as érték){
		utasítások
	}

Jól használható a szerkezet például tömbben való keresésre. Mondjuk, ha kíváncsiak vagyunk rá, hogy a $gyumolcs tömb tartalmazza-e a "banán"-t, akkor azt így deríthetjük ki:

	$talalt = false;
	foreach ($gyumolcs as $valami){
		if ($valami == "banán"){
			$talalt = true;
			break;
		}
	}

A break és continue utasítások ugyanúgy használhatóak itt is, mint ciklusok esetében. Ha a tömb tartalmazza a "banán" karakterláncot, akkor az if ág végre fog hajtódni, és ekkor kiugrunk a szerkezetből. Ezután a $talalt változó vizsgálatával tudhatjuk meg, hogy volt-e banán a tömbben.
A foreach szerkezettel meg is változtathatjuk a tömb elemeit (persze egy sima ciklussal is), de ekkor már szükségünk van a kulcsra. Például, ha egy tömbben minden 2-es értékű elemet 1-esre akarunk cserélni, akkor ezt így tehetjük meg:

	$osztalyzat = array(
		"matek"    => 2,
		"biológia" => 4,
		"kémia"    => 4,
		"fizika"   => 2,
		"földrajz" => 5
	);
	foreach ($osztalyzat as $tantargy => $jegy){
		if ($jegy == 2){
			$osztalyzat[$tantargy] = 1;
		}
	}

Így minden 2-es osztályzatból 1-es lett. Hiába sikerült nagy nehezen átmennünk matekból meg fizikából, a foreach szerkezetnek köszönhetően mégis megbuktunk! Valami ilyesmik zajlanak az egyetemeken vizsgaidőszakban :). A fenti példa mutatja, hogy a kulcsnak és az értéknek adhatunk beszédesebb neveket is, ami megkönnyíti a kód megértését.
Fontos, hogy a $jegy változó ciklusmagon belüli megváltoztatásával nem változik meg a tömb adott eleme, mivel az csak egy ideiglenes változó. A foreach szerkezet két ideiglenes változója ilyen szempontból hasonlóan működik, mint a függvények paraméterei.

Olyannyira hasonló, hogy ha a tömbelemet jelölő ideiglenes változót referenciaként adjuk meg (ld. 14. lecke), akkor a megváltoztatásával már módosítani tudjuk az adott tömbelemet:

	foreach ($osztalyzat as &$jegy){
		if ($jegy == 2){
			$jegy = 1;
		}
	}

Ez a fenti foreach ciklussal egyenértékű. A kulcsot jelölő változót viszont nem adhatjuk meg referenciaként, ezért a foreach belsejében a kulcsokat nem tudjuk megváltoztatni.

Érdemes megjegyezni, hogy ha a foreach-nek átadott változó (amit be szeretnénk járni) nem tömb, akkor egy hibaüzenet fog kiíródni. Hogy ezt elkerüljük, ha nem vagyunk teljesen biztosak abban, hogy a változó tömb-e, érdemes egy feltételes ágba rakni a foreach szerkezetet:

	$bukott = false;
	if (is_array($osztalyzat)){
		foreach ($osztalyzat as $jegy){
			if ($jegy == 1){
				$bukott = true;
				break;
			}
		}
	}

Ha a bejárandó tömböt nem mi hozzuk létre, akkor általában előfordulhat az is hogy üres, vagy létre se jön, és ilyen esetben ajánlatos egy hasonló ellenőrzést végezni.

Házi feladat

1.) Van egy $admin nevű asszociatív tömbünk, aminek a kulcsai a fenti példáknak megfelelően: "id", "nev", "jelszo", "email" és "kor". Írassuk ki a tömb minden elemét egy foreach szerkezettel úgy, hogy a jelszó helyére csillag (*) karaktereket írunk, méghozzá annyi csillagot, ahány karakterből áll a jelszó! Tehát a kimenet ilyesmi legyen:
id: 1
nev: Hörb
jelszo: ***************
email: example@example.com
kor: 23

Azt felhasználhatjuk a programban, hogy a jelszó a "jelszo" kulcsú elemben van.

Megoldás

Először is hozzuk létre a tömböt:

		$admin = array(
			"id"     => 1,
			"nev"    => "Hörb",
			"jelszo" => "megfejthetetlen",
			"email"  => "example@example.com",
			"kor"    => 23
		);

Az előző leckében lévő egyik példaprogram pont ilyen kimenetet adott, csak kiírta a jelszót. Azt kell módosítanunk úgy, hogy ha a kulcs a "jelszo", akkor mást írjon ki. Például:

		foreach ($admin as $kulcs => $ertek){
			if ($kulcs == "jelszo"){
				print $kulcs.": ";
				$hossz = strlen($ertek);
				for ($n = 0; $n < $hossz; ++$n){
					print "*";
				}
				print "<br />";
			}
			else{
				print $kulcs.": ".$ertek."<br />";
			}
		}

Egy karakterlánc hosszát az strlen() függvénnyel tudjuk megállapítani (ld. 11. lecke). A fenti programban kihasználtuk azt a gyorsítási lehetőséget, hogy a karakterlánc hossza nem változik a for-ciklus futása során, így annak értékét a ciklus előtt berakhatjuk egy változóba.

2.) Készítsünk egy olyan függvényt, aminek ha átadjuk paraméterként az utolsó példában szereplő $osztalyzat nevű asszociatív tömböt, visszaadja a benne lévő jegyek átlagát!

Megoldás

A jegyek átlagát úgy számítjuk ki, hogy összeadjuk a jegyeket, és ezt elosztjuk a jegyek számával. Egy lehetséges megoldás:

		function atlag($osztalyzat){
			$i = 0;
			$szum = 0;
			foreach ($osztalyzat as $jegy){
				++$i;
				$szum += $jegy;
			}
			return $szum / $i;
		}

Az $i változóba kerül a jegyek száma, a $szum-ba pedig az összegük. A foreach minden egyes lépésénél növeljük $i-t eggyel, $szum-ot pedig az aktuális jegy értékével. A tömb kulcsaira (tantárgyak neve) itt nincs szükség, úgyhogy a foreach egyszerűbb formáját is használhatjuk.