lukaszczerwinski
c++ programmer
Designed by Lukasz Czerwinski
Copyright © 2009 by Lukasz Czerwinski   •   All Rights reserved   •
Algorytmy warunkowe
HOME
O MNIE
PROJEKTY
C++
GALERIA
Pablo Software Solutions
Określenie zagadnienia...

Chcemy w prosty sposób stworzyć zbiór algorytmów ogólnych, które będą działać tylko na wybranych elementach kontenerów, np.:

std::copy_if( tablica.begin(), tablica.end(), tylko_liczby_dodatnie );

std::for_each_if(
    tablica.begin(),
    tablica.end(),
    tylko_wartosci_niepuste
);

Rozwiązanie...

W tym artukule, moglibyśmy napisać zbiór algorytmów warunkowych, zamknąć je z pliku nagłówkowym i zachować do przyszłego użycia. Spróbujemy jednak rozwiązać problem troszkę inaczej. Nie będziemy tworzyć nic nowego. Zamiast tego wykorzystamy już to co jest.

Standardowe podejście polega na tym, że wołamy jakis algorytm ogólny. Jako parametry przekazujemy mu zakres na którym ma pracować oraz dla niektórych algorytmów predykat. Ten predykat mówi algorytmowi, że nie chcemy brać pod uwagę wszystkich elementów z zakresu, lecz tylko te które go spełniają. Spójrzmy na przykład na algorytm
remove_if

std::remove_if( tablica.begin(), tablica.end(), mniej_niz_10 );

Niestety niektóre algorytmy nie mają miejsca na predykat.
std::copy, std::for_each przyjmują tylko dwa paramatry, dwa iteratory. Pierwszy z nich wskazuje początek zakresu na którym chcemy operować a drugi koniec. Algorytm wykona jakąś operację biorąc pod uwagę każdy z tych elementów.

I tutaj właśnie jest miejsce, żeby zmienić to zachowanie. Nie możemy przekazać do tych algorytmów predykatu. To już wiemy i tego nie zmienimy. Ale co by się stało, gdybyśmy stworzyli inteligentny iterator. Jego inteligencja polegała by na tym, że wiedziałby po których elementach kolekcji (zakresu) ma się iterować.

Teraz zadanie określenia po jakich elementach należy się iterować nie należy do algorytmu, tylko do iteratorów. Jak już wspominałem na początku artykułu nie będziemy tworzyć nic nowego. Użyjemy gotowych rozwiązań. Takie gotowe iteratory istnieją w bibliotece boost::iterators, a ponieważ ich zadanie można opisać, jako filtrowanie elementów spełniających predykat nazywają się filter_iterator.

Użycie
boost::filter_iterator jest bardzo proste. Do jego utworzenia wykorzystujemy funkcje boost::make_filter_iterator. Funkcja ja występuje w dwóch wersjach. Pierwsza przyjmuje typ predykatu jako parametr szablonu, druga przyjmuje obiekt predykatu jako swój własny parametr.

boost::make_filter_iterator< Predykat /*jako parametr szablonu*/>(
    iterator,
    iterator=end()
)

boost::make_filter_iterator(
    Predykat /*jako parametr funkcji*/,
    iterator,
    iterator = end()
)


Ich użycie pokazane jest poniżej

// uzyto boost::assign::operator+=
std::vector< int > pelnaTablica;
pelnaTablica += 1, -5, 2, -9, 4, 0, 7, -8;
std::vector< int > pustaTablica;

struct is_positive_number {
    bool operator()(int x) { return x > 0; }
};

int main(){
    std::copy(
        boost::make_filter_iterator< is_positive_number >(
            pelnaTablica.begin()
        ),
        boost::make_filter_iterator< is_positive_number >(
            pelnaTablica.end()
        ),
        std::back_inserter( pustaTablica )
    );

    std::for_each(
        boost::make_filter_iterator(
            is_positive_number(),
            pelnaTablica.begin()
        ),
        boost::make_filter_iterator(
            is_positive_number(),
            pelnaTablica.end()
        ),
        std::cout << boost::lambda::_1
    );
}

W przykładzie z algorytmem
std::for_each w celu drukowania elementów na ekranie użyto jako obiektu funkcyjnego wyrażenia lambda std::cout << boost::lambda::_1. A czy można użyć wyrażenia lambda jako predykatu? Oczywiście, oto przepisany przykład z algorytmem std::for_each, gdzie zarówno w miejsce predykatu jak i funktora użyto wyrażenia lambda.

std::for_each(
    boost::make_filter_iterator(
        boost::lambda::_1 > 0,
        pelnaTablica.begin()
    ),
    boost::make_filter_iterator(
        boost::lambda::_1 > 0,
        pelnaTablica.end()
    ),
    std::cout << boost::lambda::_1
);


Możliwe jest także połączenie filtrujących iteratorów z
BOOST_FOREACH. Oto przykład poniżej

std::vector< int > tablica;
// boost::assign::operator+=
tablica += 2, 5, 6, -1, 0, 4, -9, 7, ...

BOOST_FOREACH(
    int current,
    std::make_pair(
        boost::make_filter_iterator(
            boost::lambda::_1 % 2,
            tablica.begin()
        ),
        boost::make_filter_iterator(
            boost::lambda::_1 % 2,
            tablica.end()
        )
    )
)
{
    // zrob cos dla kazdego current
}

wstecz
HOME
O MNIE
PROJEKTY
C++
GALERIA
Na skróty

boost::iterator