Ebben a leírásban egy igazából az oldal profiljába nem vágó, de gondolkodtató feladatot írok le, amellyel a napokban találkoztam, majd adok rá egy megoldást is.

Excel csoportok színezése

A kitűzött cél

Az oldalnak nem nagyon vág a profiljába ez a leírás, talán annyiban, hogy a programozáshoz némileg köze van, mégis ajánlom elolvasni. Javaslom, hogy először a feladatkiírást értelmezve próbáld meg megoldani magad a feladatot (a megadott szabályoknak megfelelően), majd ha sikerült, vagy feladtad, nézd meg az általam írt megoldást a részletes leírással. Ha Te máshogy oldanád meg, azt kérlek írd meg egy hozzászólásba, egy darabig nekem gondolkoznom kellett rajta.

A feladat a következő: adott egy tetszőleges méretű Excel tábla, amely egyik oszlopa alapján rendezve van. Ezen oszlopban előfordulnak megegyező elemek. Például:

CMStutorial.hu videó – új cikk felvétele
CMSWordpress és Facebook összekötése
CMSWordPress manuális frissítése 3.0-ra
CMSLetölthető a Wordpress 3.0
Grafika76 ingyenesen letölthető vektor grafika
GrafikaPhotoshop fotó retusálás – előtte és utána – 2
GrafikaPhotoshop fotó retusálás – előtte és utána – 1
GrafikaÚjabb ingyenes ikon gyűjtemény
KódolásHosszú linkek és szövegek megtörése
KódolásMegosztás-sáv készítése jQuery-vel, összesített képekkel
KódolásCSS 3 gradiens a Firefox 3.6-ban
KódolásHogyan használj bármilyen betűtípust a weben? (Cufón)

A feladat: színezzük ki ezt a táblát két színnel úgy, hogy azok a sorok, amelyeknél megegyezik az első oszlop tartalma azonos színt kapjanak.

CMStutorial.hu videó – új cikk felvétele
CMSWordpress és Facebook összekötése
CMSWordPress manuális frissítése 3.0-ra
CMSLetölthető a Wordpress 3.0
Grafika76 ingyenesen letölthető vektor grafika
GrafikaPhotoshop fotó retusálás – előtte és utána – 2
GrafikaPhotoshop fotó retusálás – előtte és utána – 1
GrafikaÚjabb ingyenes ikon gyűjtemény
KódolásHosszú linkek és szövegek megtörése
KódolásMegosztás-sáv készítése jQuery-vel, összesített képekkel
KódolásCSS 3 gradiens a Firefox 3.6-ban
KódolásHogyan használj bármilyen betűtípust a weben? (Cufón)

További kikötések:

  • Az Excelen belül kell maradni. Semmilyen külső eszköz nem használható
  • Csak Feltételes formázás és Excel-képlet használható

A következőkben az én megoldásom szerepel, de gondolkodtató feladványról lévén szó, ajánlom, hogy először próbáld magadtól megoldani!

Alapesetben ez egy egyszerű probléma, a megoldása triviális. Tároljuk egy változóban az utolsóként használt színt, majd soronként haladva a táblázatban ellenőrizzük, hogy az aktuális sor első oszlopa megegyezik-e az előzővel, ha nem, akkor színt cserélünk. Minden sort fessünk be olyanra, amilyen szín épp a változóban van. PHP-szerű pszeudó-kódban:

<?php
$color = 0;
$last = array('');	/* Így inicializálunk, plusz egy feltételt megúszunk a ciklusban */
$lines = file($bemeneti_file); /* Beolvassuk a filet */
foreach($lines as &$line) {
	if($line[0] != $last[0]) {	/* Új érték az első oszlopban */
		$color = ($color == 0) ? 1 : 0;	/* Színcsere */
	}
	colorize($line, $color);	/* Képzeletbeli színező függvény */
}
?>

Csakhogy az Excel-ben nem nagyon lehet változókat és ciklust használni, és ez így túl egyszerű lenne, nincs benne kihívás. Más megoldás után kell néznünk!

Közelítsük meg más irányból a dolgot: ki tudjuk-e vajon számolni, hogy hány egyedi név fordult elő a táblában az aktuális sor fölött? Nem akarok senkit rémisztgetni az ezt a számolást végrehajtó köztes próbálkozásaimmal; a végleges megoldásomban a kulcsszó: rekurzió. Hogyan tudjuk ezt egyszerűen kiszámolni? Tegyük fel, hogy tudjuk, hogy az előző sor előtt hány egyedi érték volt. Ezután az aktuális sorra vonatkozó értéket megkaphatjuk, ha megvizsgáljuk, hogy az előző sorban levő értékkel megegyezik-e a jelenlegi sorban levő érték; ha igen, akkor ugyanannyi egyedi érték volt az aktuális sor felett, mint az előző felett, ha nem, akkor nem. Így a ciklust visszavezethetjük a rekurzió műveletre!

Egy új oszlopban (legyen a C) az első sorba vegyük fel a 0 értéket (mert ugye rekurziónál mindig kell, hogy legyen érték, aminél már nincs újabb hívás!). Utána a következő sorba szúrjuk be a =C1+HA(A1=A2;0;1) képletet, és húzzuk végig a teljes táblázaton. A rekurzív hívást láttán sokan azonnal letesznek egy-egy algoritmus használatáról, mert az úgyis lassú lesz (tévedés, algoritmustól függ, hogy mennyiszer számol feleslegesen), de ha megnézzük ebben az esetben ez O(n)-es (vagyis annyi hívás van, ahány sor), tehát nem lassabb, mint a fenti ciklust használó kódunk.

Ekkor a C oszlopban megkaptuk, hogy hány egyedi elem volt az adott sor fölött. Innen már triviális a megoldás, a változó használatát egyszerűen kikerüljük két feltételes formázási szabály használatával. Ha ugyanis egy sorban ez az érték páros, akkor az egyik színnel festjük, ha nem, akkor a másikkal (=MARADÉK(INDIREKT('C'&SOR();2)=0 és nem egyenlő párja).

Mi a leírás tanulsága? Azon kívül, hogy minden, egy pár perc alatt összedobható scripttel megoldható probléma kiterjeszthető fél órás gondolkodást igénylő problémává? Megfelelő módon ábrázolt adatokkal (legyen az a leírásban levő hozzáadott számérték, egy self-balancing bináris fa, akármi) sok probléma megoldható elegáns (itt: rövid, hatékony) algoritmussal.

Harder

Harder

Profil

Korábbi hozzászólások (7)

A régi blogon érkezett hozzászólások archívuma. Új hozzászólást már nem lehet írni.

  1. Fefy

    Nekem van egy másik megoldás, ami szerintem időben hasonló lefutású, viszont látványosabban elkülönülnek a "páros és páratlan" csoportok. A megoldás hasonló, mint ami a tutorial-ban is van.
    Tehát a következőt kell csinálni:
    - c1-be fel kell venni a 0-t, mint kezdőértéket (ha 1-et veszünk fel, akkor mindenhol felcserélődnek a számok, tehát ezzel lehet változtatni, hogy a "páros" vagy a "páratlan" csoport legyen kiszínezve).
    - c2-ben megvizsgáljuk, hogy c1 0-t tartalmaz-e.
    - - Ha 0-t, akkor megnézzük, hogy a2=a1?
    - - - Ha egyenlő, akkor 0-t írunk, ha nem, akkor 1-et.
    - Ha c1 nem 0-t (vagyis 1-et tartalmaz), akkor megnézzük, hogy a2=a1?
    - - Egyenlőség esetén 1-et írunk, különben 0-t.

    Tehát maga a képlet: =HA(C1=0;HA(A2=A1;0;1);HA(A2=A1;1;0)) Ezt pedig végighúzzuk a táblázatban :)
    Röviden tehát annyit csinálok, hogy ha másik csoportra váltok, akkor szimplán 0-ból 1-et (1-ből 0-t) csinálok. Ezután már egy egyszerű =0 feltételes formázási szabállyal eldönthető, hogy melyik sort kell színezni.

  2. BlackY

    Tényleg... :) Így gyakorlatilag teljesen a php-szerű pszeudó-kódot kapod, csak a ciklusrekurzió cserével. Nekem ez a megoldás eszembe se jutott.

    Bár a leírás végén levő általános érvényű igazságot (gondoljuk végig mit és hogyan tárolunk/számolunk) ezzel kevésbé tudtam volna illusztrálni. :)

    BlackY

  3. Fefy

    BlackY: a php-ban programozás hátrányai, hogy már az excel-t is így "programozom" :D

    Egyébként ha jól emlékszem az ACM-es időkre, akkor a dupla "if" közelebb van O(n)-hez, mint a maradékos osztásos megoldásod, de egyszer majd tesztelem egy nagy táblázaton (csak ehhez visszább kellene venni a lustaságból :D).

    Továbbá a fentieknél ha nem lett volna megkötés a csak excel függvény+feltételes formázás, akkor én simán makróztam volna. Kb 10-15 sornyi vbscript-el meg lehetett volna oldani az egészet segédoszlop nélkül :)

  4. BlackY

    Az O(n) osztály annyit jelent, hogy az algoritmus futási idejére (legyen ez az f függvény) n-elemű (n hosszú) bemenetre igaz, hogy f(n)

  5. Fefy

    Na lassan tényleg nekiesek, hogy kiderítsem, hogy MS bácsinál futásidő szerint az összeadás + osztás, vagy a "ha" a gyorsabb, mivel lényegében ennyi a különbség, ami befolyásolhatja a futásidőt :)

    Egyébként az O(n)-hez közelebb dolgot csöppet rosszul írtam, mivel itt arra gondoltam, hogy kevesebb függvényhívás van egy bemeneti adatsorra, vagyis n bemeneti adatra x*n függvényhívás (tehát konkrétan arra a lehetséges 1/3-nyi futásidőbeli eltérésre gondoltam, amit ki is fejtettél, hogy elviekben hogyan jön ki).

    Na de részemről a téma addig lezárva, ameddig nem csinálom meg a nagy sebességtesztet/nem találok a színezésre beépített függvényt, mert tuti, hogy van vmi csak nem tudunk róla :)

  6. Fefy

    Megcsináltam a tesztet, de gyakorlatilag az excel parancsértelmezője olyan gyors, hogy egy ~950 MHz-re visszavett core2-n nincs érezhető különbség (a helyzet hasonló egy 3GHz-es HT-s Pentiumon is (Prescott mag :D)). Lényegében 2 másodperc alatt újraszámolja a teljes táblázatot (1048576 sor, vagyis a 2007-es excel maximális munkalap mérete), valamint a színezést is megcsinálja.

    Ha valakit esetleg érdekel a táblázat, akkor itt egy link, ahonnan letölthető (A-tól C-ig az én módszerem, G-től I-ig BlackY-é):
    http://data.hu/get/2762093/tutorial_hu_excel_sebesseg.xlsx (~40MB)

    A C1-ben, illetve I1-ben levő szám megváltoztatása (0-ra vagy 1-re) újraszámoltatja a teljes táblázatot, ami megmutatja, hogy melyik a gyorsabb. Akinek esetleg van egy lassabb gépe sok rammal, azt leírhatná, hogy melyik módszer a gyorsabb :)

  7. Vendi

    =HA(A2=A1;C1;NEM(C1))
    Szerintem ez egyszerűbb képlet és talán kicsivel gyorsabb is.
    Egy segédoszlop felvétele néha egyszerűbb mint makrót írni, főleg ha a makrók betöltése tiltva van.

Van hozzáfűznivalód? Írj nekem közvetlenül.

Kapcsolat