Jak wywołać kasowanie za pomocą iteratora wstecznego

181

Próbuję zrobić coś takiego:

for ( std::list< Cursor::Enum >::reverse_iterator i = m_CursorStack.rbegin(); i != m_CursorStack.rend(); ++i )
{
    if ( *i == pCursor )
    {
        m_CursorStack.erase( i );
        break;
    }
}

Jednak usuwanie wymaga iteratora, a nie odwrotnego iteratora. czy istnieje sposób przekonwertowania odwrotnego iteratora na zwykły iterator lub inny sposób usunięcia tego elementu z listy?

0xC0DEFACE
źródło
17
Nawiasem mówiąc, pisząc takie pętle, nie obliczaj wielokrotnie iteratora końcowego, jak tutaj i != m_CursorStack.rend(). Zamiast tego pisz i = m_CursorStack.rbegin(), end = m_CursorStack.rend(); i != end;. Innymi słowy, zainicjuj iterator, który możesz przechowywać w celu powtórzenia porównania - zakładając, że pozycja końcowa nie zmieni się jako efekt uboczny twojego ciała pętli.
seh
Wydaje mi się, że oczywistym pytaniem byłoby, dlaczego w ogóle to robisz. Co zyskujesz, przeglądając listę w odwrotnej kolejności? Co zyskujesz, pisząc ten kod samodzielnie, zamiast go używać std::remove?
Jerry Coffin,
I czy iterator na liście std :: wciąż jest ważny do zwiększenia po usunięciu elementu, do którego się odnosi?
Steve Jessop,
3
Chcę tylko usunąć 1 element, więc „break”, użycie „remove” pozbyłoby się tego, że dopasowanie trwa dłużej i nie robi tego, co chcę. Element, który chcę usunąć w tym konkretnym przypadku, prawie zawsze będzie końcem listy lub bardzo blisko niej, więc iterowanie w odwrotnej kolejności jest również szybsze i lepiej pasuje do problemu.
0xC0DEFACE
4
stackoverflow.com/a/2160581/12386 To mówi, że projektanci specjalnie nie definiują implementacji, ponieważ jako użytkownik nie powinniście wiedzieć ani się tym przejmować, ale @seh powyżej oczekuje, że magicznie po prostu będziemy wiedzieć, że rend () jest obliczany i drogie.
stu

Odpowiedzi:

181

Po kilku dalszych badaniach i testach znalazłem rozwiązanie. Najwyraźniej zgodnie ze standardem [24.4.1 / 1] związek między i.base () i i wynosi:

&*(reverse_iterator(i)) == &*(i - 1)

(z artykułu dr Dobbs ):

alternatywny tekst

Musisz więc zastosować przesunięcie przy pobieraniu base (). Dlatego rozwiązaniem jest:

m_CursorStack.erase( --(i.base()) );

EDYTOWAĆ

Aktualizacja do C ++ 11.

reverse_iterator ipozostaje niezmieniony:

m_CursorStack.erase( std::next(i).base() );

reverse_iterator ijest zaawansowany:

std::advance(i, 1);
m_CursorStack.erase( i.base() );

Uważam to za znacznie jaśniejsze niż moje poprzednie rozwiązanie. Użyj tego, czego potrzebujesz.

0xC0DEFACE
źródło
27
Powinieneś zanotować nieco więcej cytowanego artykułu - aby być przenośnym, powinno to wyglądać m_CursorStack.erase( (++i).base())(człowiek, robienie tego z odwrotnymi iteratorami powoduje ból głowy ...). Należy również zauważyć, że artykuł DDJ znajduje się w książce Meyera „Effective STL”.
Michael Burr,
8
Uważam ten schemat za bardziej mylący niż pomocny. Ponieważ rbegin, ri i rend faktycznie wskazują na element po prawej stronie tego, na co mają być skierowane. Schemat pokazuje, do jakiego elementu miałbyś dostęp, gdybyśmy *je mieli, ale mówimy o tym, na który element wskazywałbyś, gdybyś baseje używał, czyli jeden element po prawej stronie. Nie jestem fanem rozwiązań --(i.base())ani (++i).base()rozwiązań, ponieważ mutują one iterator. Wolę też, (i+1).base()która działa.
mgiuca
4
Odwrotne iteratory są kłamcami. Po ich odreagowaniu iterator do tyłu zwraca element przed nim . Zobacz tutaj
bobobobo,
4
Żeby było absolutnie jasne, technika ta wciąż nie może być używana w normalnej pętli for (gdzie iterator jest zwiększany w normalny sposób). Zobacz stackoverflow.com/questions/37005449/…
logidelic
1
Wygląda na to, że m_CursorStack.erase ((++ i) .base ()) byłby problemem, gdyby ++ udało mi się przejść do ostatniego elementu. czy możesz wywołać funkcję erase on end ()?
stu
16

Należy pamiętać, że m_CursorStack.erase( (++i).base())może być problem, jeśli zostanie użyty w forpętli (patrz oryginalne pytanie), ponieważ zmienia wartość i. Prawidłowe wyrażenie tom_CursorStack.erase((i+1).base())

Andrey
źródło
4
Musisz utworzyć kopię iteratora i zrobić iterator j = i ; ++j, ponieważ i+1nie działa na iteratorze, ale to jest właściwy pomysł
bobobobo
3
@ Bobobobo, możesz używać m_CursorStack.erase(boost::next(i).base())z Boost. lub w C ++ 11m_CursorStack.erase(std::next(i).base())
alfC
12

... lub inny sposób usunięcia tego elementu z listy?

Wymaga to -std=c++11flagi (dlaauto ):

auto it=vt.end();
while (it>vt.begin())
{
    it--;
    if (*it == pCursor) //{ delete *it;
        it = vt.erase(it); //}
}
slashmais
źródło
Działa urok :)
Den-Jason
@GaetanoMendola: dlaczego?
slashmais
3
Kto gwarantuje, że iteratory na liście są uporządkowane?
Gaetano Mendola
7

Zabawne, że na tej stronie nie ma jeszcze właściwego rozwiązania. Tak więc następujące jest poprawne:

W przypadku iteratora do przodu rozwiązanie jest proste:

std::list< int >::iterator i = myList.begin();
while ( ; i != myList.end(); ) {
  if ( *i == to_delete ) {
    i = myList.erase( i );
  } else {
    ++i;
  } 
}

W przypadku iteratora wstecznego musisz zrobić to samo:

std::list< int >::reverse_iterator i = myList.rbegin();
while ( ; i != myList.rend(); ) {
  if ( *i == to_delete ) {
    i = decltype(i)(myList.erase( std::next(i).base() ));
  } else {
    ++i;
  } 
}

Uwagi:

  • Możesz zbudować reverse_iterator z iteratora
  • Możesz użyć wartości zwracanej z std::list::erase
Gaetano Mendola
źródło
Ten kod działa, ale proszę wyjaśnić, dlaczego używa się next i jak bezpiecznie jest rzucić iterator do przodu na iterator do tyłu bez zawalenia się świata
Lefteris E
1
@LefterisE To nie jest obsada. Tworzy nowy iterator wsteczny z interatora. Jest to normalny konstruktor iteratora zwrotnego.
Šimon Tóth
3

Podczas korzystania z reverse_iterator„s base()metody i zmniejszanie wynikiem tu pracuje, to warto zauważyć, że reverse_iteratorS nie podano ten sam status jak zwykłe iterators. Zasadniczo powinieneś preferować zwykłe iterators reverse_iterator( s) const_iteratororaz const_reverse_iterators (s) z dokładnie takich powodów. Zobacz doktora Dobbsa, aby uzyskać szczegółowe omówienie przyczyny.

Adam Rosenfield
źródło
3
typedef std::map<size_t, some_class*> TMap;
TMap Map;
.......

for( TMap::const_reverse_iterator It = Map.rbegin(), end = Map.rend(); It != end; It++ )
{
    TMap::const_iterator Obsolete = It.base();   // conversion into const_iterator
    It++;
    Map.erase( Obsolete );
    It--;
}
Nismo
źródło
3

A oto fragment kodu, który przekształca wynik wymazywania z powrotem w odwrotny iterator w celu wymazania elementu w kontenerze podczas iteracji w odwrotnej kolejności. Trochę dziwne, ale działa nawet po skasowaniu pierwszego lub ostatniego elementu:

std::set<int> set{1,2,3,4,5};

for (auto itr = set.rbegin(); itr != set.rend(); )
{    
    if (*itr == 3)
    {
        auto it = set.erase(--itr.base());
        itr = std::reverse_iterator(it);            
    }
    else
        ++itr;
}
etham
źródło
2

Jeśli nie musisz wymazywać wszystkiego w trakcie, a następnie rozwiązać problem, możesz użyć idiomu usuwania-usuwania:

m_CursorStack.erase(std::remove(m_CursorStack.begin(), m_CursorStack.end(), pCursor), m_CursorStack.end());

std::removezamienia wszystkie elementy w kontenerze, które pasują pCursordo końca, i zwraca iterator do pierwszego pasującego elementu. A późniejerase użycie zakresu usunie się z pierwszego dopasowania i przejdzie do końca. Kolejność niepasujących elementów zostaje zachowana.

Może to działać szybciej, jeśli używasz std::vector , gdzie wymazywanie w środku zawartości może wymagać dużo kopiowania lub przenoszenia.

Lub oczywiście, powyższe odpowiedzi wyjaśniające użycie reverse_iterator::base()są interesujące i warte poznania, aby rozwiązać dokładnie określony problem, uważam, że std::removelepiej pasuje.

gavinbeatty
źródło
1

Chciałem tylko coś wyjaśnić: w niektórych z powyższych komentarzy i odpowiedzi przenośna wersja do wymazywania jest wymieniona jako (++ i) .base (). Jednak chyba, że ​​coś mi brakuje, poprawną instrukcją jest (++ ri) .base (), co oznacza, że ​​„zwiększasz” reverse_iterator (nie iterator).

Wczoraj wpadłem na potrzebę zrobienia czegoś podobnego i ten post był pomocny. Dziękuję wszystkim.

użytkownik1493570
źródło
0

Aby uzupełnić odpowiedzi innych i ponieważ natknąłem się na to pytanie, szukając std :: string bez większego powodzenia, oto odpowiedź z użyciem std :: string, std :: string :: erase i std :: reverse_iterator

Mój problem polegał na usunięciu nazwy pliku obrazu z pełnego ciągu nazwy pliku. Pierwotnie został rozwiązany za pomocą std :: string :: find_last_of, ale badam alternatywny sposób z std :: reverse_iterator.

std::string haystack("\\\\UNC\\complete\\file\\path.exe");
auto&& it = std::find_if( std::rbegin(haystack), std::rend(haystack), []( char ch){ return ch == '\\'; } );
auto&& it2 = std::string::iterator( std::begin( haystack ) + std::distance(it, std::rend(haystack)) );
haystack.erase(it2, std::end(haystack));
std::cout << haystack;  ////// prints: '\\UNC\complete\file\'

Używa algorytmu, iteratora i nagłówków ciągów.

fmmarques
źródło
0

iterator do tyłu jest dość trudny w użyciu. Właśnie użyłem ogólnego iteratora. „r” Zaczyna się od ostatniego elementu. Gdy znajdziesz coś do usunięcia. usuń go i zwróć następny iterator. np. po usunięciu trzeciego elementu wskaże bieżący czwarty element. i nowy trzeci. Dlatego należy zmniejszyć 1, aby przejść w lewo

void remchar(string& s,char c)
{      
    auto r = s.end() - 1;
    while (r >= s.begin() && *r == c)
    {
        r = s.erase(r);
        r -= 1;
    }
}
Mark Yang
źródło