9. hét: Generikus algoritmusok

Czirkos Zoltán, Dobra Gábor · 2019.02.27.

Heti kiegészítő feladatok

1. Feltételes kiírás, függvénypointer

Írj függvényt, amely paraméterként egy egész számokból álló tömböt kap, és kiírja az abban tárolt számokat!

Egészítsd ki a függvényed egy további paraméterrel, ahol egy unáris predikátumot kap (unary predicate), amelyet függvénypointerként vesz át! Legyen a függvény feladata most az, hogy csak azokat a számokat írja ki a tömbből, amelyekre a függvény azt mondja, hogy kellenek.

Ha mindent jól csinálsz, ez a programrész működni fog:

bool is_negative(int x) {
    return x < 0;
}

int main() {
    int numbers[5] = { 6, -5, 9, 12, -8 };
    print_if(numbers, 5, is_negative);  // -5 -8
}

2. Pointerek

Írd át a print_if() függvényedet úgy, hogy a tömb kezdőcíme és mérete helyett inkább pointereket kapjon paraméterként, amelyekkel a tömb belseje is könnyebben hivatkozható! A két pointer a tömb belsejéből egy tartományt jelöl ki, balról zárt (első elem), jobbról nyílt (utolsó utáni elem) intervallumban. Dolgozz a függvényen belül is pointeraritmetikával, ne indexeléssel!

A használat így módosul:

int main() {
    int numbers[10] = { 9, 5, -8, 4, -6, 7, 9, 1, 2, -9 };

    print_if(numbers, numbers + 10, is_negative); // -8 -6 -9
    print_if(numbers + 3, numbers + 8, is_negative); // -6
}

3. Sablonnal

Módosítsd a függvényed úgy, hogy bármilyen típusú adatra működni tudjon! Például az ábécében sloth (lajhár) előtti szavak listázása:

bool is_before_sloth(std::string x) {
    return x < "sloth";
}

int main() {
    std::string animals[5] = {
        "spiky floof",
        "disco turkey",
        "nope rope",
        "prison pony",
        "furry potato",
    };
    print_if(animals, animals + 5, is_before_sloth);
}

4. Bármilyen iterátorral

Módosítsd a függvényed úgy, hogy bármilyen típusú iterátorral működjön, ne csak pointerrel! Például std::list iterátorával is:

bool is_before_sloth(std::string x) {
    return x < "sloth";
}

int main() {
    std::list<std::string>> animals = {
        "spiky floof",
        "disco turkey",
        "nope rope",
        "prison pony",
        "furry potato",
    };
    print_if(animals.begin(), animals.end(), is_before_sloth);
}

5. Minél legyen kisebb?

Látható, hogy a predikátumok paraméterezhetőek lennének. Negatív = 0-nál kisebb (de lehetne 5-nél kisebb, 100-nál kisebb, -800-nál kisebb is). „sloth” előtt (de lehetne „formal chicken” előtt vagy „velocirabbit” előtt is). A predikátum viszont unáris kell legyen (egy paraméterű függvény), mert egy adott szóról van számról kell eldönteni, hogy kell vagy nem kell.

A probléma könnyen kezelhető egy funktor (függvényként viselkedő objektum) bevezetésével. Ehhez hasonlóan:

class Functor {
  private:
    /* tagváltozók ... */

  public:
    bool operator() (T const & x) const {
        return /* ... */;
    }
};

Egy ilyen objektum képes függvényként viselkedni, mert van függvényhívó operátora: operator() nevű tagfüggvénye. Az operátor belsejében viszont nem csak a paraméter látszik (a vizsgálandó adat), hanem a tagváltozók is elérhetőek, amik tetszőlegesen sokan lehetnek.

Tedd sablonná a print_if() predikátum paraméterét, és írj funktorokat, amelyekkel egy adott számnál kisebb számok, illetve az ábécében egy adott szót megelőző szavak írhatók ki! Ha mindent jól csinálsz:

int main() {
    int numbers[5] = { 6, -5, 9, 12, -8 };
    print_if(numbers, numbers + 5, LessThan(8));  // 6 -5 -8

    std::string animals[5] = {
        "spiky floof",
        "disco turkey",
        "nope rope",
        "prison pony",
        "furry potato",
    };
    print_if(animals, animals + 5, BeforeThan("sloth"));
}

6. Kisebb-e = Előrébb van-e

Vedd észre, hogy számokra és sztringekre a „kisebb-e”, illetve „ábécében előrébb van-e” függvények (funktorok) igazából ugyanúgy működnek, az x < y operátort használják. Egyesítsd a LessThan és a BeforeThan osztályodat egyetlen LessThan osztállyá, amelynek sablonparamétere a kezelt típus!

int main() {
    print_if(numbers, numbers + 5, LessThan<int>(8));
    print_if(animals, animals + 5, LessThan<std::string>("sloth"));
}

7. count_if()

Írj függvénysablont, amely megszámolja egy (tömb-)tartományban az adott tulajdonságú elemeket!

  • Az első két paraméter a bemeneti tartomány, [balról zárt, jobbról nyílt) intervallum (lásd fent).
  • A harmadik paraméter a predikátum.
  • A visszatérési érték a darabszám.
int main() {
    int numbers[10] = { 9, 5, -8, 4, -6, 7, 9, 1, 2, -9 };

    int cnt = count_if(numbers, numbers + 10, LessThan<int>(5));
    std::cout << "A tömbben " << cnt << " db 5-nél kisebb szám van.";
}

8. copy_if()

Írj függvénysablont, amely egy (tömb-)tartomány elemeit másolja egy másik tömbbe, de csak akkor, ha az elemek egy bizonyos feltételnek megfelelnek!

  • Az első két paraméter a bemeneti tartomány, [balról zárt, jobbról nyílt) intervallum.
  • A harmadik paraméter a cél tömb kezdőcíme.
  • A negyedik paraméter a predikátum.
  • A visszatérési érték egy pointer, amelyik azt mutatja, hogy a cél tömbben meddig lettek adatok.
int main() {
    int numbers[10] = { 9, 5, -8, 4, -6, 7, 9, 1, 2, -9 };
    int negative[10];

    int *p = copy_if(numbers, numbers + 10, negative, LessThan<int>(0));
    std::cout << "A cél tömbbe " << (p-negative) << " elem került:";
    for (int *i = negative; i != p; ++i)
        std::cout << *i << " ";
}

9. my_sort_1()

A rendezéshez kétparaméterű, bináris predikátum kell (binary predicate): a rendezés közben a kérdésünk mindig az, hogy a két vizsgált elem közül melyik való előbbre a tömbben.

Írj rendezőfüggvényt, amely paraméterként egy tömb kezdőcímét és méretét kapja, továbbá egy bináris predikátumot (komparátort), amely a rendezési szempontot adja meg!

int main() {
    int numbers[10] = { 9, 5, -8, 4, -6, 7, 9, 1, 2, -9 };

    my_sort_1(numbers, 10, less);       // a<b -> növekvő sorrend
    my_sort_1(numbers, 10, greater);    // a>b -> csökkenő sorrend
}

Ha nem tetszik, hogy meg kell írni a less és greater függvényeket, használhatod a beépített funktorokat: std::less<int> és std::greater<int>, az #include <functional> fejlécfájlból. Ügyelj arra, hogy ne függvénypointereket használj a kódodban; az előbb említett funktorok osztályok, amelyeket példányosítani is kell.

10. my_sort_2()

Írd meg úgy a rendezőfüggvényt, hogy ne tömböt és méretet, hanem a fent látott módon pointerekkel megadott tartományt vegyen át paraméterként!

int main() {
    int numbers[10] = { 9, 5, -8, 4, -6, 7, 9, 1, 2, -9 };

    my_sort_2(numbers + 2, numbers + 8, less);       // csak a belsejét
}

Extra: írd meg úgy a rendezést, hogy a pointernek csak a ++, *, == és != operátorát használod! Tehát ptr-ptr tilos, ptr[i] tilos, ptr+int tilos. Később meglátod, ennek mi a jelentősége.

11. Fordítási hiba

Mi a hiba az alábbi kódban?

Legalább háromféleképpen lehet javítani. Hogyan?

#include <iostream>

template <typename T>
void print_if(T* arr, size_t siz, bool (*pred)(T)) {
    for (size_t i = 0; i < siz; ++i)
        if (pred(arr[i]))
            std::cout << arr[i];
}

bool is_negative(int x) {
    return x < 0;
}

bool is_before_sloth(std::string const & x) {
    return x < "sloth";
}

int main() {
    int numbers[5] = { 6, -5, 9, 12, -8 };
    print_if(numbers, 5, is_negative);

    std::string animals[5] = {
        "spiky floof",
        "disco turkey",
        "nope rope",
        "prison pony",
        "furry potato",
    };
    print_if(animals, 5, is_before_sloth);
}