Prog 2 0. verseny feladatsor

Utolsó módosítás: 2008. november 13.

Ez a feladatsor az első verseny első, november 13. csütörtök reggeli turnusához tartozik.

Le lehet tölteni az összes példa be- és kimenetet és példa forráskódot egyben: z0p.tgz.

0. feladat (z0f0): lyukak kitöltése jobbra

A program bemenete egy sorból áll, amiben tizennégy szám áll szóközzel elválasztva. Mindegyik szám 0 és 1000 közti egész. A kimenetben ugyanennyi számot kell kiírni. Ahol a bemenetben pozitív szám áll, ott a kimenetben a megfelelő szám ugyanaz. Ahol viszont a bemenetben nulla áll, ott ezt a kimenetben az ez előtti utolsó nem nulla számmal kell helyettesíteni.

(A bemenetet a standard bemenetről olvassa, a kimenetet a standard kimenetre írja. A kimenetben a számokat a legrövidebb decimális alakban kell kiírni, közöttük egy szóköz áll, de a sor végén nincs szóköz; a sort újsor jellel kell lezárni.)

Például ha a bemenet a következő:

716 102 0 0 682 314 76 319 460 0 762 0 278 748

akkor a kimenet:

716 102 102 102 682 314 76 319 460 460 762 762 278 748

Még néhány példa.

z0f0be0 -> z0f0ki0

z0f0be1 -> z0f0ki1

z0f0be2 -> z0f0ki2

z0f0be3 -> z0f0ki3

z0f0be4 -> z0f0ki4

1. feladat (z0f1): körlevél több soros mezővel

Ez a feladat az 1. mintafeladatsor 6. feladatának (körlevél írása) módosítása. A feladat eléggé hasonlít, így csak a különbségeket sorolom fel. A megoldáshoz érdemes a mintafeladat s1f6.c mintamegoldásból kiindulni.

A feladat abban különbözik az eredetitől, hogy a sablonban van egy új fajta jelölő, amihez egy több sorból álló mező tartozik.

A sablonban kétféle mezőjelzés van: közönséges, és hosszú jelzés. A közönséges jelzés ugyanúgy néz ki, mint eddig: @i, és legfeljebb 50 van belőle. A hosszú jelzés három karakterből áll: ezek a @m majd egy újsor jel. Hosszú jelzésből pontosan egy van (ez az 50 közönséges jelzésbe nem számít bele), és ez mindig sor elején van.

A rekordok megnyitó jelzése egy sor, amiben egy pozitív szám áll, de ez a szám az eredeti feladattal ellentétben nem feltétlenül egy, hanem 1-től 50-ig bármi lehet. Ez a szám adja meg, hány sorból áll a hosszú jelzéshez tartozó mező. A rekordban ez után minden mező jelzéshez egy mező érték tartozik. A közönséges jelölőkhöz tartozó jelölő továbbra is egy sor, és ennek a végén álló újsor jelet továbbra sem kell behelyettesíteni a levélbe. A hosszú jelölőhöz azonban egy több sorból (legalább egy sorból) álló mező tartozik, ennek a sorainak a számát a rekord megnyitó jelzése adja meg. A hosszú jelölő értékéhez hozzá tartozik minden sorának a végén álló újsor jel, ezzel együtt kell tehát a mezőt a jelölő helyére behelyettesíteni. A mezők olyan sorrendben vannak felsorolva a rekordban, ahogy a hozzájuk tartozó jelölők a mintában elhelyezkednek.

A levelet továbbra is úgy kapjuk, hogy a sablonban a jelölőket a mezők értékére kicseréljük.

Íme néhány letölthető példa. (A 0. példát ajánlom megnézni, mert ez olvashatóbb a többinél.)

z0f1be0 -> z0f1ki0

z0f1be1 -> z0f1ki1

z0f1be2 -> z0f1ki2

z0f1be3 -> z0f1ki3

z0f1be4 -> z0f1ki4

z0f1be5 -> z0f1ki5

Végül egy tanács: a rekord kezdősorából a számot a sscanf(line, "%d", &i) hívással olvassuk be az int típusú i változóba.

2. feladat (z0f2): csempézés

Ebben a feladatban a programnak egy egyszerű mintát kell eltolással vagy tükrözéssel ismételnie, hogy egy nagyobb mintát kapjunk. Először egy egyszerűbb változtatot mutatok be megoldással, utána elmondom a bonyolultabb feladatot, amit ez alapján meg kell oldaniuk.

Az egyszerűbb változatban a bemenet első sora négy egész számot tartalmaz, ezek H, W, K, M. Az első szám 0 <= H <= 20 a kis minta sorainak számát adja meg, a második 0 <= W <= 20 a kis minta oszlopainak a számát, a harmadik 0 <= K <= 10 a vízszintesen egymás mellé rakott példányok számát, a negyedik 0 <= M <= 2 az ismétlés módját. Ez után a bemenetnek még H sora van, mindegyik sor W nyomtatható ascii karakterből (ideértve a szóközt is) áll, majd egy újsor jelből áll. Ezek a sorok meghatározzák a minta egy elemi celláját, ami téglalap alakú. Ezt a téglalapot kell egymás mellett vízszintesen K-szor megismételni, hogy a kimenetet, a nagy mintát megkapjuk.

Ha M = 0, akkor az ismétlések egyszerűen egymás eltolt példányai, a következő mindig W karakterrel az előzőtől jobbra. Ha M = 1, akkor minden második ismétlés vízszintesen tükrözve van, így minden példányt úgy kapunk, hogy az előzőt ennek jobb szélére tükrözünk. Végül ha M = 1, akkor szintén tükrözéssel kapjuk a példányokat, de a vízszintesen tükrözött példányoknak el kell hagyni a bal szélső oszlopát, ezt az oszlopot az előző példány jobb szélső oszlopa úgymond eltakarja.

Ha egy karaktert vízszintesen tükrözünk, akkor a következő négy pár karakter egymásra cserélődik ki: () [] <> /\, a többi karakter pedig nem változik.

Nézzünk egy-egy példát mindegyik ismétlési módra! Az egyik bemenet.

3 10 6 0
  ____(D) 
/(  _  )\,
 []] []]  
az ehhez tartozó kimenet pedig a következő.
  ____(D)   ____(D)   ____(D)   ____(D)   ____(D)   ____(D) 
/(  _  )\,/(  _  )\,/(  _  )\,/(  _  )\,/(  _  )\,/(  _  )\,
 []] []]   []] []]   []] []]   []] []]   []] []]   []] []]  
A második esetben a bemenet
5 5 8 1
\   _
 \ ( 
 \ ( 
 \ ( 
|   |
ehhez pedig a következő kimenet tartozik.
\   __   /\   __   /\   __   /\   __   /
 \ (  ) /  \ (  ) /  \ (  ) /  \ (  ) / 
 \ (  ) /  \ (  ) /  \ (  ) /  \ (  ) / 
 \ (  ) /  \ (  ) /  \ (  ) /  \ (  ) / 
|   ||   ||   ||   ||   ||   ||   ||   |
Végül a harmadik tükrözési módot ez a bemenet mutatja be.
4 3 6 2
  O
 -+
  |
 / 
Ebből ez a kimenet lesz.
  O    O    O  
 -+-  -+-  -+- 
  |    |    |  
 / \  / \  / \ 

Itt van ez a három példa is, és még három másik.

z0f6be0 -> z0f6ki0

z0f6be1 -> z0f6ki1

z0f6be2 -> z0f6ki2

z0f6be3 -> z0f6ki3

z0f6be4 -> z0f6ki4

z0f6be5 -> z0f6ki5

Nézzük ennek a mintamegoldását, ami egyben letölthető innen: z0f6.c.

#include 
#include 
#include 

void
die(char *msg) {
	fprintf(stderr, "%s\n", msg);
	exit(1);
}

char
mirror_char(char *key, char c) {
	while (key[0] && key[1]) {
		if (c == key[0])
			return key[1];
		if (c == key[1])
			return key[0];
		key += 2;
	}
	return c;
}

A fenti mirror_char függvény egyetlen c karaktert tükröz, ha a key kulcs egy páros hosszú nullára végződő sztring, ami a tükörkép karakterpárokat tartalmazza.

#define TEMPLATE_MAXHEIGHT 20
#define TEMPLATE_MAXWIDTH 20

int 
template_height,
template_width;
char
template[TEMPLATE_MAXHEIGHT][TEMPLATE_MAXWIDTH];
int
num_copies,
copy_mode;
char
mirrored[TEMPLATE_MAXHEIGHT][TEMPLATE_MAXWIDTH];

void
read_template(void) {
	char line[TEMPLATE_MAXWIDTH + 5];
	int r;
	for (r = 0; r < template_height; r++) {
		if (!fgets(line, TEMPLATE_MAXWIDTH + 5, stdin))
			die("eof or error reading template line");
		if (template_width != strlen(line) - 1) 
			die("template line length wrong");
		memcpy(template[r], line, template_width);
	}
}

A négy paramétert (H, W, K, M) rendre négy változóban tároljuk: template_height, template_width, num_copies, copy_more. A read_template függvényt már ezek kitöltése után kell meghívni (no meg miután az első sort a bemenetről beolvastuk): ez a template mátrixba beolvassa az elemi cella karaktereit (de újsorokat vagy nullákat nem tárol el).

void
mirror_template(void) {
	int r, c;
	for (r = 0; r < template_height; r++) {
		for (c = 0; c < template_width; c++)
			mirrored[r][c] = mirror_char("()<>[]\\/", template[r][template_width - c - 1]);
	}
}

A mirror_template függvény elkészíti az elemi cella vízszintes tükörképét a mirrored mátrixba.

void
output(void) {
	int row, copy;
	char *p; int l;
	for (row = 0; row < template_height; row++) {
		for (copy = 0; copy < num_copies; copy++) {
			if (0 == copy % 2 || 0 == copy_mode) {
				p = template[row]; l = template_width;
			} else if (2 == copy_mode) {
				p = mirrored[row] + 1; l = template_width - 1;
			} else {
				p = mirrored[row]; l = template_width;
			}
			fwrite(p, l, 1, stdout);
		}
		putchar('\n');
	}
}

int
main(void) {
	char line[500];
	if (!fgets(line, sizeof(line), stdin))
		die("error reading parameters");
	if (4 != sscanf(line, "%d%d%d%d", 
		&template_height, &template_width, &num_copies, ©_mode
	))
		die("error parsing parameters");
	if (
		TEMPLATE_MAXHEIGHT < template_height || template_height < 0 ||
		TEMPLATE_MAXWIDTH < template_width || template_width < 0 ||
		2 < copy_mode || copy_mode < 0
	)
		die("invalid parameters");
	read_template();
	mirror_template();
	output();
	return 0;
}

Végül a main függvény beolvassa a négy paramétert, meghívja az első két segédfüggvényt, majd meghívja az output függvényt, ami soronként végigmenve kiírja a kimenetet.

Az ön feladata ettől abban különbözik, hogy a mintát nem csak oldalra, hanem lefele is mozgatni kell.

Az igazi feladatban tehát a bemenet első sora hat egész paraméterből áll szóközzel elválasztva: ezek a H, W, K, M, K', M'. Az első négy paraméter jelentése megegyezik az előzőekkel. Miután az elemi cellát vízszintesen egymás mellett az előző megismételtük, a kapott széles mintát függőlegesen egymás alá is meg kell ismételni K'-ször. A felső példány az eredeti, a többi példány készítésének módját az M' szabályozza hasonlóan, mint ahogy az M a vízszintes másolást irányítja.

Ha M' = 0, akkor az egymás alatti példányok egymás eltoltjai H soronként. Ha M' = 1, akkor minden második példány függőlegesen tükrözve van, a tükörtengely az előző széles minta alján helyezkedik el. Ha M' = 2, akkor a tükörtengely az előző széles minta alsó sorának közepe, így a rendesen álló példányok alsó sora átfedi a függőlegesen tükrözött példányok felső sorát, ez utóbbi sort tehát nem írjuk ki.

Amikor egy karaktert függőlegesen tükrözünk, akkor a következő két pár cserélődik ki egymásra: ,' /\. A többi karakter nem változik.

Íme néhány példa.

z0f2be0 -> z0f2ki0

z0f2be1 -> z0f2ki1

z0f2be2 -> z0f2ki2

z0f2be3 -> z0f2ki3

z0f2be4 -> z0f2ki4

z0f2be5 -> z0f2ki5

A bonyolultabb feladatot kell tehát megoldania, ehhez felhasználhatja az egyszerűbbre adott mintamegoldást. A módosított forrást a SIO rendszerben a z0f2 feladathoz kell feltölteni.

3. feladat (z0f3): algebrai kifejezések kirajzolása

Ebben a feladatban algebrai kifejezéseket fogunk átalakítani prefix írásmódról hagyományos síkbeli matematikai jelölésre.

Algebrai kifejezésnek tekintjük a következőket: változó, kis szám konstans, két algebrai kifejezés hatványozása, hányadosa, szorzata, különbsége vagy összege.

A prefix írásmódban az operátor jelét az argumentumai elé írjuk. Ennek az az előnye, hogy a jelölés zárójelek nélkül is egyértelmű. Változónak tekintjük az angol ábécé kisbetűit, ezek prefix jelölésben is egyszerűen egy kisbetűbel vannak megadva. Hasonlóan a 0 és 9 közti számokat kis számkonstansnak tekintjük, ezeket a prefix kifejezésben egy számjegy jelzi. (Nagyobb számot nem írunk le.)

Ha x és y két algebrai kifejezés, prefix alakjuk X és Y, akkor értelmes kifejezést kapunk, ha az x-et az y-ik hatványra emeljük, ennek az xy hatványnak a prefix alakja ^XY. Hasonlóan az x/y hányados, az x*y szorzat, az x-y különbség és az x+y összeg prefix alakje rendre /XY, *XY, -XY, +XY.

Az algebrai kifejezéseknek definiáljuk a hagyományos jelölését is. Ez egy karakterekből álló táblázat, aminek a magasságán és szélességén és a tartalmán kívül még ki van jelölve az egyik sora alapsornak.

A változók és konstansok hagyományos írása egy 1 széles 1 magas táblázat (az alapsor egyértelmű), aminek egyetlen karaktere a megfelelő betű illetve számjegy.

Bonyolultabb kifejezések hagyományos alakját kisebb táblázatok összerakásával kapjuk meg. Ilyenkor az új táblázat pont akkora, hogy az összes összerakott táblázat éppen beleférjen. Ahova semmilyen táblázat nem kerül, ott szóközök állnak.

Hatványozásnál az alap és a kitevő hagyományos alakát úgy rakjuk össze, hogy a kitevőhöz tartozó táblázat bal alsó sarka épp összeérjen az alaphoz tartozó táblázat jobb felső sarkával. A hatvány hagyományos alakjának az alapsora az alap alapsora.

Ha egy algebrai kifejezés két részkifejezés hányadosa, akkor a két részkifejezés hagyományos alakja egymás alá kerül úgy, hogy egy sor kimarad köztük. Vízszintesen úgy kell igazítani a két résztáblázatot, hogy a közepük egymás fölött legyen, vagy, ha ez paritási okból lehetetlen, akkor a szélesebb táblázat közepe 1/2 karakterrel a keskenyebb közepe alatt legyen. A hányados táblázata jobb és bal oldalt is egy oszloppal szélesebb, mint a két rész közül a szélesebbik. Az alapsor az a sor, amelyik a két részkifejezés között szabadon maradt, ezt a sort a táblázatban végig mínusz [-] jelekkel kell feltölteni.

Ha egy algebrai kifejezés két részkifejezés összege, különbsége, vagy szorzata, akkor a két részkifejezés hagyományos alakját úgy kell összerakni, hogy a két rész alapsora ugyanabba a sorba essen, ez lesz az új kifejezés alapsora. Vízszintesen a két résztáblázat között szorzat esetén egy, összeg vagy különbség esetén három oszlopot ki kell hagyni. A szorzat esetén nincs műveleti jel; összegnél és különbségnél a három kihagyott oszlop közül a középsőnek és az alapsornak a metszetébe egy plusz [+] illetve mínusz [-] karaktert kell elhelyezni.

Mielőtt azonban összeraknánk a kifejezéseket, néha még zárójeleket kell beszúrni. Egy hagyományos alakot úgy kell bezárójelezni, hogy a táblázatot bal és jobb oldalt egy-egy oszloppal kibővítjük, ezeket a táblázat teljes magasságában bal [(] illetve jobb [)] zárójelekkel kell kitölteni. Az új táblázat alapsora ugyanaz, ahova a kisebb táblázat alapsora került. Egy kifejezés hagyományos alakját bizonyos esetekben ennek bezárójelezésével kell helyettesíteni, majd az eredeti táblázat helyett ezt használni nagyobb kifejezés építéséhez.

Pontosan a következő esetekben kell zárójelezni. Zárójelezni kell egy szorzatot, hatványt, vagy hányadost, ha egy hatvány alapjában áll; zárójelezni kell egy összeget vagy különbséget, ha ez egy szorzat tényezője vagy egy hatvány alapja. Olyan kifejezésekkel nem foglalkozunk, amiben egy szorzat jobb oldali tényezője szorzat, vagy ahol egy összeg vagy különbség jobb oldali tagja összeg vagy különbség.

Amikor kiírunk egy kifejezést hagyományos alakban, akkor a táblázatot soronként kell kiírni, a sorok végéről a szóközöket elhagyva, és minden sor után újsor jelet kell írni. Végül az egész kifejezés után még egy újsort ki kell írni.

Lássunk egy példát. A következő kifejezést nézzük.

(a/b)1/2 - (c + d)(1 + a) - (a - d)/b2 + b2 / (ac)

Ennek a prefix alakja a következő.

+--^/ab/12*+cd+1a/-ad^b2/^b2*ac

A hagyományos alakja egész kifejezésként kiírva pedig az alábbi.

      1
     ---
      2                                  2
( a )                         a - d     b
(---)    - (c + d) (1 + a) - ------- + -----
( b )                           2       a c
                               b

A feladatban a bemenet több sorból áll, minden sorban egy algebrai kifejezés prefix alakja van. A kimenetben ezeknek a kifejezéseknek a hagyományos alakját kell fölrajzolni.

Minden kifejezés olyan, hogy a prefix alakja legfeljebb 100 karakterből áll.

Néhány példa a következő.

z0f7be0 -> z0f7ki0

z0f7be1 -> z0f7ki1

z0f7be2 -> z0f7ki2

Ezt az egyszerűbb feladatot a z0f7.c program oldja meg. Íme a forráskód.

#include 
#include 
#include 

int
max(int x, int y) {
	return x < y ? y : x;
}

#define PLANBUF_LEN 512

#define PREC_ATOM 0
#define PREC_POWER 2
#define PREC_QUOTIENT 3
#define PREC_PRODUCT 4
#define PREC_SUM 5

struct
plan {
	int width;
	int height;
	int depth;
	int prec;
	int head;
	const struct plan *lhs;
	const struct plan *rhs;
} planbuf[PLANBUF_LEN];
int
plancnt;

void
init(void) {
	plancnt = 0;
}

struct plan *
allocplan(void) {
	struct plan *r;
	r = planbuf + plancnt++;
	if (PLANBUF_LEN < plancnt)
		exit(1);
	r->lhs = r->rhs = 0;
	r->prec = r->head = -1;
	r->width = r->height = r->depth = 32767;
	return r;
}

const struct plan *
parenthisize(const struct plan *l) {
	struct plan *p;
	p = allocplan();
	p->head = '(';
	p->lhs = l;
	p->width = l->width + 2;
	p->height = l->height;
	p->depth = l->depth;
	p->prec = PREC_ATOM;
	return p;
}

const struct plan *
layout(const char **formulap) {
	struct plan *p;
	char head;
	const struct plan *l, *r;
	p = allocplan();
	p->head = head = *(*formulap)++;
	if (isdigit(head) || islower(head)) {
		p->width = 1;
		p->height = 1;
		p->depth = 0;
		p->prec = PREC_ATOM;
	} else {
		l = layout(formulap);
		r = layout(formulap);
		if ('^' == head) {
			if (PREC_POWER <= l->prec)
				l = parenthisize(l);
			p->width = l->width + r->width;
			p->height = l->height + r->depth + r->height;
			p->depth = l->depth;
			p->prec = PREC_POWER;
		} else if ('/' == head) {
			p->width = 2 + max(l->width, r->width);
			p->height = 1 + l->height + l->depth;
			p->depth = r->height + r->depth;
			p->prec = PREC_QUOTIENT;
		} else if ('*' == head) {
			if (PREC_PRODUCT < l->prec)
				l = parenthisize(l);
			if (PREC_PRODUCT <= r->prec)
				r = parenthisize(r);
			p->width = 1 + l->width + r->width;
			p->height = max(l->height, r->height);
			p->depth = max(l->depth, r->depth);
			p->prec = PREC_PRODUCT;
		} else if ('+' == head || '-' == head) {
			if (PREC_SUM <= r->prec)
				r = parenthisize(r);
			p->width = 3 + l->width + r->width;
			p->height = max(l->height, r->height);
			p->depth = max(l->depth, r->depth);
			p->prec = PREC_SUM;
		} else 
			exit(1);
		p->lhs = l;
		p->rhs = r;
	}
	return p;
}

#define PAPER_WIDTH 500
#define PAPER_HEIGHT 100

char
paper[PAPER_HEIGHT][PAPER_WIDTH];
int
box_height, box_width;

void
erase(const struct plan *p) {
	int r, c;
	box_height = p->height + p->depth;
	box_width = p->width;
	if (PAPER_HEIGHT < box_height || PAPER_WIDTH < box_width)
		exit(1);
	for (r = 0; r < box_height; r++)
		for (c = 0; c < box_width; c++)
			paper[r][c] = ' ';
}

void
output(void) {
	int r, w, c;
	for (r = 0; r < box_height; r++) {
		w = box_width;
		while (' ' == paper[r][w - 1])
			w--;
		for (c = 0; c < w; c++)
			putchar(paper[r][c]);
		putchar('\n');
	}
	putchar('\n');
}

void
paint(const struct plan *p, int row, int col) {
	int r, c;
	int head;
	if (
		row - p->height + 1 < 0 || 
		box_height < row + p->depth ||
		col < 0 ||
		box_width < col + p->width
	)
		exit(1);
	head = p->head;
	switch (head) {
		case '(':
			for (r = row - p->height + 1; r <= row + p->depth; r++) {
				paper[r][col] = '(';
				paper[r][col + p->width - 1] = ')';
			}
			paint(p->lhs, row, 1 + col);
			break;
		case '^':
			paint(p->lhs, row, col);
			paint(p->rhs, row - p->lhs->height - p->rhs->depth, col + p->lhs->width);
			break;
		case '/':
			for (c = col; c < col + p->width; c++)
				paper[row][c] = '-';
			paint(p->lhs, row - 1 - p->lhs->depth, col + (p->width - p->lhs->width)/2);
			paint(p->rhs, row + p->rhs->height, col + (p->width - p->rhs->width)/2);
			break;
		case '*':
			paint(p->lhs, row, col);
			paint(p->rhs, row, 1 + col + p->lhs->width);
			break;
		case '+': case '-':
			paint(p->lhs, row, col);
			paper[row][1 + col + p->lhs->width] = head;
			paint(p->rhs, row, 3 + col + p->lhs->width);
			break;
		default:
			if (isdigit(head) || islower(head))
				paper[row][col] = head;
			else
				exit(1);
			break;
	}
}

void
paint_main(const struct plan *p) {
	paint(p, p->height - 1, 0);
}

#define FORMULA_LEN 512

int
main(void) {
	char formulabuf[FORMULA_LEN];
	const struct plan *p;
	const char *formula;
	while (fgets(formulabuf, FORMULA_LEN, stdin)) {
		formula = formulabuf;
		init();
		p = layout(&formula);
		if ('\n' != *formula)
			exit(1);
		erase(p);
		paint_main(p);
		output();
	}
	return 0;
}

A kód megértéséhez a következőt kell tudni. Minden kifejezést két lépésben dolgozunk fel.

Az első lépésben, amit a layout függvény hajt végre, értelmezzük a prefix kifejezést, és minden részkifejezéséhez csinálunk egy struct plan típusú leírót. A leíróban eltárolunk elég információt ahhoz, hogy a kifejezés visszaállítható legyen, erre szolgálnak a head, lhs, rhs mezők; valamint az ehhez tartozó hagyományos kifejezés méretét is eltároljuk: a width mező a tábla szélességét tartalmazza, a height mező a magasságot az alapsortól a táblázat tetejéig, a depth pedig a mélységet az alapsor alatt. A prec mezőt arra használjuk, hogy eldöntsük, kell-e zárójelezni a kifejezést, ha igen, akkor a zárójelezett változathoz egy újabb leírót hozunk létre.

A második fázisban a leírókat bejárva kirajzoljuk a kifejezéseket. A paper karakter-mátrixba kerül az eredmény: ennek elég nagy részét kitöltjük szóközökkel, majd rekurzívan hívjuk a paint eljárást, ami egy leírót és a részkifejezés alapsorának a bal szélső mezőjének pozícióját kapja argumentumként, és a papar mátrixra rajzol. Végül a mátrix alapján kiírjuk az eredményt.

Mármost önnek azzal kell bővíteni a programot, hogy faktoriálist is kezeljen. Az új szabályok a következők.

Ha x algebrai kifejezés, amelynek X a prefix alakja, akkor x! (azaz x faktoriális) is algebrai kifejezés, és ennek prefix alakja !X. Ha az x részkifejezésnek megtaláltuk a hagyományos alakját, akkor az x! hagyományos alakját úgy kapjuk, hogy az előbbihez hozzárakunk még egy oszlopot jobbra, és ebbe az oszlopba az alapsorba (ami a részkifejezésnek és a teljesnek is az alapsora), rakunk egy felkiáltójel [!] karaktert. Előbb azonban a részkifejezéshez tartozó táblázatot zárójelbe kell rakni, ha ez a részkifejezés hatvány, hányados, szorzat, összeg, vagy különbség. A teljes faktoriálist sosem kell zárójelbe rakni.

Példák.

z0f3be0 -> z0f3ki0

z0f3be1 -> z0f3ki1

A módosított feladat megoldását a SIO-ra a z0f3 feladathoz töltse föl.