2012-12-27 12 views
8

Obecnie mam ten kod i działa:Łącząc C++ standardowych algorytmów poprzez zapętlenie tylko raz

string word="test,"; 
string::iterator it = word.begin(); 
for (; it != word.end(); it++) 
{ 
    if (!isalpha(*it)) { 
     break; 
    } 
    else { 
     *it = toupper(*it); 
    } 
} 
word.erase(it, word.end()); 
// word should now be: TEST 

chciałbym uczynić go bardziej zwarty i czytelny go:

  1. Komponowanie istniejącej standardowej C++ algorytmów (*)
  2. Wykonać pętli tylko raz

(*) mi zakładając, że c ombining istniejących algorytmów sprawia, że ​​mój kod bardziej czytelny ...

Alternatywnym rozwiązaniem

Oprócz definiowania algorytmu zwyczaj transform_until, jak sugeruje jrok, może to być możliwe określenie adaptera zwyczaj iterator że iterację korzystania podstawowym iteratorem, ale przedefiniuj operatora *(), modyfikując bazowe odniesienie przed zwróceniem go. Coś takiego:

template <typename Iterator, typename UnaryFunction = typename Iterator::value_type (*)(typename Iterator::value_type)> 
class sidefx_iterator: public std::iterator< 
         typename std::forward_iterator_tag, 
         typename std::iterator_traits<Iterator>::value_type, 
         typename std::iterator_traits<Iterator>::difference_type, 
         typename std::iterator_traits<Iterator>::pointer, 
         typename std::iterator_traits<Iterator>::reference > 
{ 
    public: 
    explicit sidefx_iterator(Iterator x, UnaryFunction fx) : current_(x), fx_(fx) {} 

    typename Iterator::reference operator*() const { *current_ = fx_(*current_); return *current_; } 
    typename Iterator::pointer operator->() const { return current_.operator->(); } 
    Iterator& operator++() { return ++current_; } 
    Iterator& operator++(int) { return current_++; } 
    bool operator==(const sidefx_iterator<Iterator>& other) const { return current_ == other.current_; } 
    bool operator==(const Iterator& other) const { return current_ == other; } 
    bool operator!=(const sidefx_iterator<Iterator>& other) const { return current_ != other.current_; } 
    bool operator!=(const Iterator& other) const { return current_ != other; } 
    operator Iterator() const { return current_; } 

    private: 
    Iterator current_; 
    UnaryFunction fx_; 
}; 

Oczywiście jest to nadal bardzo surowy, ale powinny dać pomysł. Z powyższej karty, mógłbym wtedy napisać następujące:

word.erase(std::find_if(it, it_end, std::not1(std::ref(::isalpha))), word.end()); 

z następujących zdefiniowane wcześniej (co może być uproszczona przez jakiś szablon magii):

using TransformIterator = sidefx_iterator<typename std::string::iterator>; 
TransformIterator it(word.begin(), reinterpret_cast<typename std::string::value_type(*)(typename std::string::value_type)>(static_cast<int(*)(int)>(std::toupper))); 
TransformIterator it_end(word.end(), nullptr); 

Jeśli średnia obejmowałyby takiego adaptera użyłbym, ponieważ oznaczałoby to, że był bezbłędny, ale ponieważ tak nie jest, prawdopodobnie zachowam pętlę taką, jaka jest.

Taki adapter pozwoli na ponowne wykorzystanie istniejących algorytmów i mieszanie ich w inny sposób nie jest możliwe dzisiaj, ale może mieć także wady, które ja prawdopodobnie widokiem na chwilę ...

+0

W moim bieżącym kodzie jest jedna pętla. Chodzi mi o to, że po przepisaniu wciąż będzie jedna pętla. –

+1

Przedwczesny exodus oparty na '! Isalpha (* it)' jest jedyną rzeczą, którą widzę potencjalnie powstrzymując cię od osiągnięcia tego, czego myślę, że szukasz, i szczerze mówiąc, wszystkiego, co może zrobić to dla twojego (i nie mogę zobaczysz wszystko, co by to zrobiło) byłoby tak zawiłe, że masz czynnik jasności, który wyszedłby przez okno. Pewnie bym się trzymał tego, co masz. – WhozCraig

+0

Dziękuję wszystkim za inspirujące odpowiedzi. Na razie trzymam się mojego obecnego kodu. Wierzę, że 'boost :: transform_iterator' jest najbliższą rzeczą, której szukałem. Ten przypadek użycia zostanie objęty adapterem iteratora "efektów ubocznych", czymś takim, który będzie używany w następujący sposób: 'word.erase (std :: find_if (sidefx_iterator (word.begin(), :: toupper), word.end (), std :: not1 (:: isalpha)), word.end()); ' –

Odpowiedz

9

Nie sądzę, jest to czysty sposób, aby to zrobić z jednym standardowym algorytmu.Żadna, o której wiem, nie bierze predykatu (potrzebujesz jednego, aby zdecydować, kiedy przerwać) i pozwala modyfikować elementy sekwencji źródłowej.

Możesz napisać swój własny ogólny algorytm, jeśli naprawdę chcesz zrobić to w "standardowy" sposób. Nazwijmy to, hmm, transform_until:

#include <cctype> 
#include <string> 
#include <iostream> 

template<typename InputIt, typename OutputIt, 
     typename UnaryPredicate, typename UnaryOperation> 
OutputIt transform_until(InputIt first, InputIt last, OutputIt out, 
         UnaryPredicate p, UnaryOperation op) 
{ 
    while (first != last && !p(*first)) { 
     *out = op(*first); 
     ++first; 
     ++out; 
    } 
    return first; 
} 

int main() 
{ 
    std::string word = "test,"; 
    auto it = 
    transform_until(word.begin(), word.end(), word.begin(), 
        [](char c) { return !::isalpha(static_cast<unsigned char>(c)); }, 
        [](char c) { return ::toupper(static_cast<unsigned char>(c)); }); 
    word.erase(it, word.end()); 
    std::cout << word << '.'; 
} 

Jest wątpliwe, czy ten jest lepszy niż to, co masz :) Czasami zwykły dla pętli jest najlepszy.

+5

+1, To jest tak dobre, jak myślę, że OP będzie. Chociaż prawdopodobnie powinien trzymać się planu A i po prostu zachować pętlę, ta odpowiedź jest całkiem niezłym pomysłem, który polega na napisaniu niestandardowego transformatora kontenerowego, odpowiedniej nazwie i wciąż odpowiada na pytanie OP. Nawet jeśli OP go nie używa, wciąż jest dobrą odpowiedzią. – WhozCraig

+0

Przyjąłem tę odpowiedź, ponieważ wynikowy kod jest krótki i czytelny, i wyraża ogólny algorytm, który byłby ogólnie przydatny. Wszystkie istniejące algorytmy STL mają stały iterator końcowy i wymagają pętli w całym kontenerze. Myślę, że jest to ogólna potrzeba, aby mieć warunkowy koniec. –

0

Po lepszego zrozumienia Twoje pytanie, mam pomysł, który może działać, ale wymaga Boost.

można użyć transform_iterator który nazywa toupper na wszystkich znaków i użyć jej jako inputiterator do find_if lub remove_if. Nie jestem wystarczająco zaznajomiony z Boostem, aby podać przykład.

Jak @jrok wskazuje, transform_iterator tylko transformuje wartość podczas iteracji i nie modyfikuje oryginalnego kontenera. Aby obejść ten problem, zamiast obsługiwać tę samą sekwencję, należy skopiować do nowej, używając czegoś takiego jak remove_copy_if. Kopiuje tak długo, dopóki predykat NIE jest prawdziwy, więc potrzebowałby std::not1. To zastąpi obudowę remove_if.

Użyj polecenia std::copy, aby kopiować, dopóki iterator nie zostanie zwrócony przez std::find_if, aby druga sprawa mogła działać.

Na koniec, jeśli wyjściowy ciąg znaków jest pusty, do jego wyniku potrzebny będzie iterator typu std::inserter.

+0

To pętla dwa razy - OP chciał tego uniknąć. – jrok

+0

Wygląda na to, że w twojej implementacji są dwie pętle ... Czy jestem w błędzie? –

+0

@ MariJosé Ah lepiej zrozumieć teraz twoje pytanie, tak, masz rację, są 2 pętle. –