14. gyakorlat: generikus algoritmusok
Függvényekre mutató pointerek. A gyakorlat első felében a generikus gondolkodást bemutató feladatok vannak – magas szinten. Az utolsó feladat, a generikus rendezés megvalósítása a típus nélküli pointerek kezelését mutatja be – alacsony szinten.
1 Karakterek számlálása típusok szerint
Írjunk C függvényt, amelyik egy sztringben meg képes számolni, hogy hány bizonyos tulajdonsággal rendelkező karakter van (pl. kisbetűk vagy számjegyek, esetleg írásjelek). Hogy mi ez a tulajdonság, az is a függvény paramétere legyen.
Megoldás
A sztringen végiglépkedés, a karakterek számlálásának módja
független attól, hogy konkrétan milyen karaktert keresünk.
Az „e” betűs függvény a karaktert int-ként veszi
át, hogy kompatibilis legyen az islower() és hasonló
függvényekkel.
#include <stdio.h>
#include <ctype.h>
int e_betu_e(int c) {
return c=='E' || c=='e';
}
/* A feladat megoldasa ez a fuggveny. */
/* A "feltetel" parameter: egy darab int parameteru (c), int
visszateresi ertekkel rendelkezo fuggvenyre mutato pointer. */
int karakter_szamol(char *str, int (*feltetel)(int c)) {
int darab, i;
darab=0;
for (i=0; str[i]!=0; ++i)
if (feltetel(str[i]))
darab++;
return darab;
}
int main() {
char szoveg[]="Ernoke 4 eves (jovore 5 lesz).";
printf("%d e vagy E betű\n", karakter_szamol(szoveg, e_betu_e));
printf("%d space\n", karakter_szamol(szoveg, isspace));
printf("%d kisbetű\n", karakter_szamol(szoveg, islower));
printf("%d számjegy\n", karakter_szamol(szoveg, isdigit));
return 0;
}
2 Numerikus integrálás
Integráljunk egy függvényt egy korlátos intervallumon numerikusan! Vagyis számoljuk ki az alatta lévő területet. Közelítsük ezt téglalapokkal! Írjunk generikus algoritmust, amely bármely függvényen képes dolgozni!
Megoldás
- Az
xtengelyendxlépésközzel haladunk. - Mindenhol kiszámítjuk
f(x)értékét. - Így meghatározható a rajzon látható téglalapok területe.
- Az integrál közelítő értéke ezek összege: ∑f(x)*dx.
Az integrálandó f(x) függvényt paraméterként vesszük át.
Mivel ez a példában egy egyváltozós, valós függvény, C-ben egy olyan
függvényként jelenik meg, amelynek egy double paramétere,
és double visszatérési értéke van. A paraméter
típusa tehát:
double (*f)(double)
Az ezt megvalósító függvény pedig az alábbi. (A dx-szel való
szorzást kiemelhettük a szummán kívülre, hogy gyorsabb legyen a program.)
#include <math.h>
double integral(double (*f)(double), double ettol, double eddig, double dx) {
double osszeg=0.0, x;
for (x=ettol; x<eddig; x+=dx)
osszeg+=f(x); // ∑f(x)
return dx*osszeg; // dx * ∑f(x)
}
double sajatf(double x) {
return 5*x*x + 2*x - 24.3;
}
int main() {
double t;
t = integral(sajatf, 14.3, 29.2, 0.1);
printf("sajatf -> %g\n", t);
t = integral(sin, 0, 3.1415927, 0.05);
printf("sin -> %g\n", t);
return 0;
}
Természetesen akár a könyvtári sin() függvényt
is integrálhatjuk, hiszen annak is double sin(double)
a típusa.
3 Összeg, szorzat: akkumuláció
Írjunk függvényt, amely paraméterként kap két egész számot (a és b), és összegzi a két intervallum közötti számokat (beleértve a-t és b-t is).
Megoldás
int osszeg(int a, int b) {
int szum, i;
szum=0;
for (i=a; i<=b; i+=1)
szum=szum+i;
return szum;
}
Alakítsuk át ezt úgy, hogy ne csak összegezni, hanem szorozni is lehessen vele. Ehhez az összeadást ki kell cserélnünk egy akkumuláló műveletre, és természetesen a kezdeti értéket is ki kell cserélnünk egy paraméterre (hiszen 0-nál a szorzat mindig 0 lenne).
Megoldás
int akkumulal(int a, int b, int (*akkum)(int, int), int kiindul) {
int akk, i;
akk=kiindul;
for (i=a; i<=b; i+=1)
akk=akkum(akk, i);
return akk;
}
Gondoljunk bele: így olyan összegzések is megvalósíthatóak (pl. kivonás,
osztás, négyzetösszeg, stb.), amikre az összegzés függvény megírója eredendően
nem is gondolt. A függvény tovább általánosítható lenne, ha az i+=1 lépést
is egy függvényre cserélnénk le.
Számoljuk ki ezzel a függvénnyel az 1…6 számok összegét és az 1…6 számok szorzatát (faktoriális)!
Megoldás
int osszegzo(int a, int b) {
return a+b;
}
int szorzo(int a, int b) {
return a*b;
}
int main() {
printf("1-tol 6-ig osszeg: %d\n", akkumulal(1, 6, osszegzo, 0));
printf("1-tol 6-ig szorzat: %d\n", akkumulal(1, 6, szorzo, 1));
}
4 double-öket rendező függvény
Írjunk függvényt, amely paraméterként kap egy double elemekből
álló tömböt, és rendezi azt! A rendezés szempontja (növekvő, csökkenő, abszolútérték
szerint növekvő stb.) is legyen a függvény paramétere!
Megoldás
#include <stdio.h>
#include <math.h>
/* rendezi a tombot a megadott szempont szerint */
void double_rendez(double tomb[], int meret, int (*kisebb_e)(double a, double b)) {
int i;
for (i=0; i<meret-1; ++i) {
int lk=i;
int j;
double temp;
for (j=i+1; j<meret; ++j)
if (kisebb_e(tomb[j], tomb[lk]))
lk=j;
temp=tomb[lk];
tomb[lk]=tomb[i];
tomb[i]=temp;
}
}
/* igaz, ha a<b */
int kisebb(double a, double b) {
return a<b;
}
/* igaz, ha |a|<|b| */
int abszolut_kisebb(double a, double b) {
return fabs(a)<fabs(b);
}
int main() {
int i;
double tomb[10]={1.2, 5.6, 9, -1.4, -6, 5, 9.1, 11, 0, -12};
double_rendez(tomb, 10, kisebb);
for (i=0; i<10; ++i)
printf("%7.2f", tomb[i]);
printf("\n");
double_rendez(tomb, 10, abszolut_kisebb);
for (i=0; i<10; ++i)
printf("%7.2f", tomb[i]);
printf("\n");
return 0;
}
A fenti függvények nem követik az
strcmp()konvenciót: az összehasonlító függvények nem kompatibilisek azzal. Azonban nincs is erre szükség itt még, hiszen a rendezendő adatok típusa adott.
Gondoljuk meg: szétválasztottuk a rendezés algoritmusát az összehasonlítás menetétől. Ennek több előnye van:
- Külön tudjuk megvalósítani őket.
- Az összehasonlítás algoritmusa cserélhető anélkül, hogy a rendező algoritmust módosítani kellene.
- A rendező függvény használója olyan rendezési szempontot is megadhat, amelyre a rendező függvény írója „álmában sem gondolt”.
5 A qsort() használata
Írjunk összehasonlító függvényeket a qsort()-hoz, amelyekkel:
- Egy egészekből álló tömb növekvő sorrendbe rendezhető.
- Egy sztringekből álló tömb csökkenő sorrendbe rendezhető.
Megoldás
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
int integer_osszehasonlito(const void *pelso, const void *pmasodik) {
const int *elso=(const int *)pelso;
const int *masodik=(const int *)pmasodik;
if (*elso < *masodik)
return -1;
if (*elso > *masodik)
return 1;
return 0;
}
int sztring_csokkeno(const void *pelso, const void *pmasodik) {
/* -1-gyel megszorzom, es akkor csokkenobe lesz rendezve!
* a parameterek megforditasa is jo lenne:
* return strcmp(pmasodik, pelso); */
return -1*strcmp((const char *) pelso, (const char *) pmasodik);
}
int main() {
enum { MERET=15, SZAVAKMERET=5 };
/* 5 db, max 19 betus szo */
char szavak[SZAVAKMERET][20]={"mango", "pomelo", "papaja", "grapefruit", "lime"};
int intek[MERET];
int i;
/* veletlen elemek */
srand(time(0));
for (i=0; i<MERET; i++)
intek[i]=rand()%100;
/* rendezes es kiiras */
/* itt az int-et batran sizeofolhatom */
qsort(intek, MERET, sizeof(intek[0]), integer_osszehasonlito);
for (i=0; i<MERET; i++)
printf("%d ", intek[i]);
printf("\n");
/* a sztringeket azert sizeofolom, mert a karaktertomb
meretere vagyok kivancsi, nem pedig a sztring hosszara! */
qsort(szavak, SZAVAKMERET, sizeof(szavak[0]), sztring_csokkeno);
for (i=0; i<SZAVAKMERET; i++)
printf("%s ", szavak[i]);
printf("\n");
return 0;
}
6 Saját generikus rendező függvény
Alakítsuk át az előbbi rendezőfüggvényünket úgy (bármelyik algoritmust is
választottuk), hogy ne double, hanem tetszőleges elemekkel
dolgozzon!
Megoldás
Ehhez a függvénynek void* pointerrel kell átvennie a tömböt
(mivel a típusait nem ismeri), és ugyancsak paraméter kell legyen az egyes
elemek mérete is. Az elemek cseréje nem végezhető el értékadással, de
segítségül hívhatjuk a memcpy függvényt, amely adott méretű
memóriaterületet másol. Az „ideiglenes változó” helyébe – amely a háromlépéses
cseréhez kell – is egy külön, típus nélküli memóriaterület lép. Mivel ennek
mérete függvényparaméter, dinamikusan foglaljuk. A hasonlító függvény is
void*-okat kell várjon, nem pedig double-öket.
void rendez(void *tomb,
int meret, size_t elemmeret,
int (*kisebb_e)(void const *pa, void const *pb)) {
int i;
void *temp;
temp=malloc(elemmeret);
for (i=0; i<meret-1; ++i) {
void *pi=(char*)tomb + i*elemmeret;
void *plk=pi;
int j;
for (j=i+1; j<meret; ++j) {
void *pj=(char*)tomb + j*elemmeret;
if (kisebb_e(pj, plk))
plk=pj;
}
memcpy(temp, plk, elemmeret); /* temp=lk */
memcpy(plk, pi, elemmeret); /* lk=i */
memcpy(pi, temp, elemmeret); /* i=temp */
}
free(temp);
}
A teljes program letölthető innen: genrend.c.
