Як викликати стирання за допомогою зворотного ітератора


181

Я намагаюся зробити щось подібне:

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

Однак стирання приймає ітератор, а не зворотний ітератор. чи є спосіб перетворити зворотний ітератор у звичайний ітератор чи іншим способом видалити цей елемент зі списку?


17
З іншого боку, при написанні подібних циклів не слід обчислювати повторно ітератор кінця, як це робиться тут i != m_CursorStack.rend(). Натомість пишіть i = m_CursorStack.rbegin(), end = m_CursorStack.rend(); i != end;. Тобто ініціалізуйте ітератор, який ви можете тримати навколо для неодноразового порівняння - припускаючи, що кінцеве положення не зміниться як побічний ефект вашого петлі.
seh

Мені здається, очевидним тут буде питання, чому ти взагалі робиш це. Що ви отримуєте від переходу списку в зворотному напрямку? Що ви отримуєте, написавши цей код самостійно, а не використовуючи std::remove?
Джеррі Труну

І чи ітератор у списку std :: все ще має значення для збільшення після стирання елемента, на який він посилається?
Стів Джессоп

3
Я хочу лише видалити 1 елемент, таким чином, 'break;', використовуючи 'Remove', я позбудусь будь-якого відповідника, який займає більше часу, і не робити того, що я хочу. Елемент, який я хочу видалити в цьому конкретному випадку, майже завжди буде в кінці списку або дуже близьким до нього, тому повторення в зворотному порядку також швидше і краще підходить до проблеми.
0xC0DEFACE

4
stackoverflow.com/a/2160581/12386 Це говорить про те, що дизайнери спеціально не визначають реалізацію, оскільки ви як користувач не повинні знати і не піклуватися, але @seh вище очікує, що ми магічно просто знаємо, що rend () обчислюється і дорого.
Ст

Відповіді:


181

Після ще кількох досліджень та тестувань я знайшов рішення. Мабуть, згідно зі стандартом [24.4.1 / 1], зв'язок між i.base () та i становить:

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

(зі статті доктора Доббса ):

alt текст

Тому вам потрібно застосувати зміщення при отриманні бази (). Тому рішення таке:

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

EDIT

Оновлення для C ++ 11.

reverse_iterator iне змінюється:

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

reverse_iterator iрозширений:

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

Я вважаю це набагато зрозумілішим, ніж моє попереднє рішення. Використовуйте те, що вам потрібно.


27
Ви повинні взяти до уваги трохи більшу статтю, яку ви цитували - щоб бути переносним, вираз повинен бути m_CursorStack.erase( (++i).base())(людина, якщо цей матеріал із зворотними ітераторами змушує мене боліти голову ...). Слід також зазначити, що стаття DDJ включена в книгу Меєра «Ефективна STL».
Майкл Берр

8
Я вважаю цю діаграму більш заплутаною, ніж корисною. Оскільки rbegin, ri та rend насправді вказують на елемент праворуч від того, на що вони намальовані, вказують. На діаграмі показано, до якого елемента ви б зверталися, якби ви *їх, але ми говоримо про те, на який елемент ви би вказували, якби ви baseїх, який є одним елементом праворуч. Я не є таким прихильником рішень --(i.base())чи (++i).base()рішень, оскільки вони мутують ітератор. Я вважаю за краще, (i+1).base()яка працює також.
mgiuca

4
Зворотний ітератор є брехунами .. коли deferenced, зворотний ітератор повертає елемент перед ним . Дивіться тут
bobobobo

4
Щоб бути абсолютно зрозумілим, ця методика все ще не може бути використана в нормальному для циклу (де ітератор збільшується нормально). Див stackoverflow.com/questions/37005449 / ...
logidelic

1
m_CursorStack.erase ((++ i) .base ()) здається, що це буде проблемою, якби ++ я змусив вас пройти останній елемент. ви можете викликати стирання в кінці ()?
Ст

16

Зверніть увагу, що це m_CursorStack.erase( (++i).base())може бути проблемою, якщо використовується в forциклі (див. Оригінальне запитання), оскільки це змінює значення i. Правильний вираз єm_CursorStack.erase((i+1).base())


4
Вам потрібно створити копію ітератора і зробити iterator j = i ; ++j, тому що i+1він не працює на ітераторі, але це правильна ідея
bobobobo

3
@bobobobo, ви можете використовувати m_CursorStack.erase(boost::next(i).base())Boost. або в С ++ 11m_CursorStack.erase(std::next(i).base())
alfC

12

... або інший спосіб видалити цей елемент зі списку?

Для цього потрібен -std=c++11прапор (для auto):

auto it=vt.end();
while (it>vt.begin())
{
    it--;
    if (*it == pCursor) //{ delete *it;
        it = vt.erase(it); //}
}

Працює шарм :)
Ден-Джейсон

@GaetanoMendola: чому?
slashmais

3
Хто гарантує вам, що ітератори в списку замовлені?
Гаетано Мендола

7

Смішно, що на цій сторінці поки немає правильного рішення. Отже, правильне:

У випадку ітератора прямого рішення рішення прямо:

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

У разі зворотного ітератора вам потрібно зробити те ж саме:

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;
  } 
}

Примітки:

  • Ви можете сконструювати reverse_iteratorітератор
  • Ви можете використовувати повернене значення std::list::erase

Цей код працює, але, будь ласка, поясніть, чому використовується наступний і як безпечно передавати ітератор прямого переключення на зворотний ітератор без руйнування світу
Lefteris E

1
@LefterisE Це не акторський склад. Він створює новий зворотний ітератор з інтератора. Це звичайний конструктор зворотного ітератора.
Šimon Tóth

3

Хоча тут використовується метод reverse_iterator's base()і декрементування результату працює, варто відзначити, що reverse_iterators не надано такого ж статусу, як звичайні iterators. Загалом, вам слід віддати перевагу регулярним iterators до reverse_iterators (а також const_iterators і const_reverse_iterators) саме з таких причин. Дивіться журнал доктора Доббса для поглибленого обговорення того, чому.


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--;
}

3

І ось фрагмент коду для перетворення результату стирання назад у зворотний ітератор, щоб стерти елемент у контейнері під час повторення в зворотному. Трохи дивно, але це працює навіть при стиранні першого або останнього елемента:

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;
}

2

Якщо вам не потрібно стирати все, як ви рухаєтесь далі, тоді для вирішення проблеми ви можете використовувати ідіому стирання-видалення:

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

std::removeпоміняє всі елементи в контейнері, які відповідають pCursorкінцю, і повертає ітератор до першого елемента відповідності. Тоді eraseвикористання діапазону буде стерто з першого матчу та піде до кінця. Порядок невідповідних елементів зберігається.

Це може вийти швидше для вас, якщо ви використовуєте std::vector, де стирання в середині вмісту може спричинити багато копіювання чи переміщення.

Звичайно, відповіді вище, що пояснюють використання reverse_iterator::base(), цікаві і варто знати, щоб вирішити точну заявлену проблему, я б стверджував, що std::removeце краще підходить.


1

Просто хотілося щось уточнити: в деяких з вищезазначених коментарів та відповідей портативна версія для стирання згадується як (++ i) .base (). Однак, якщо я щось не пропускаю, правильне твердження є (++ ri) .base (), це означає, що ви "збільшуєте" зворотний_ітератор (а не ітератор).

Вчора я зіткнувся з необхідністю зробити щось подібне, і ця публікація була корисною. Дякую всім.


0

Щоб доповнити відповіді інших людей, і тому, що я натрапив на це питання, шукаючи std :: string без особливого успіху, тут йде відповідь із використанням std :: string, std :: string :: erase та std :: reverse_iterator

Моя проблема полягала в тому, щоб видалити ім'я файлу зображення з повної рядка імені файлу. Спочатку було вирішено за допомогою std :: string :: find_last_of, але я досліджу альтернативний спосіб за допомогою 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\'

Для цього використовується алгоритм, ітератор та заголовки рядків.


0

зворотний ітератор досить важкий у використанні. Тому просто використовували загальний ітератор. 'r' Починається з останнього елемента. Коли знайдеш щось стерти. стерти його та повернути наступний ітератор. наприклад, при видаленні 3-го елемента він вказуватиме поточний 4-й елемент. і новий 3-й. Тож його слід зменшити на 1, щоб рухатись вліво

void remchar(string& s,char c)
{      
    auto r = s.end() - 1;
    while (r >= s.begin() && *r == c)
    {
        r = s.erase(r);
        r -= 1;
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.