Cách gọi xóa bằng trình lặp ngược


181

Tôi đang cố gắng làm một cái gì đó như thế này:

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

Tuy nhiên, erase mất một iterator chứ không phải iterator ngược. Có cách nào để chuyển đổi một trình vòng lặp ngược thành một trình vòng lặp thông thường hoặc một cách khác để loại bỏ phần tử này khỏi danh sách?


17
Bên cạnh đó, khi viết các vòng lặp như thế này, đừng lặp lại tính toán trình lặp kết thúc như bạn làm ở đây với i != m_CursorStack.rend(). Thay vào đó, hãy viết i = m_CursorStack.rbegin(), end = m_CursorStack.rend(); i != end;. Đó là, khởi tạo một trình vòng lặp mà bạn có thể giữ xung quanh để so sánh lặp đi lặp lại - giả sử rằng vị trí kết thúc sẽ không thay đổi như là một tác dụng phụ của cơ thể vòng lặp của bạn.
seh

Dường như với tôi rằng câu hỏi rõ ràng ở đây sẽ là lý do tại sao bạn làm điều này cả. Bạn có được gì khi đi qua danh sách ngược lại? Bạn có được gì khi tự viết mã này thay vì sử dụng std::remove?
Jerry Coffin

Và một trình lặp trên danh sách std :: có còn hiệu lực để tăng sau khi phần tử mà nó đề cập đã bị xóa không?
Steve Jessop

3
Tôi chỉ muốn xóa 1 phần tử, do đó, 'break;', sử dụng 'remove' sẽ loại bỏ mọi phần trùng khớp mất nhiều thời gian hơn và không làm những gì tôi muốn. Phần tử tôi muốn loại bỏ trong trường hợp cụ thể này sẽ gần như luôn luôn là phần cuối của danh sách hoặc rất gần với nó, vì vậy việc lặp lại ngược lại cũng nhanh hơn và phù hợp hơn với vấn đề.
0xC0DEFACE

4
stackoverflow.com/a/2160581/12386 Điều này nói rằng các nhà thiết kế đặc biệt không xác định việc triển khai vì bạn là người dùng không cần phải biết hoặc không quan tâm, tuy nhiên @seh ở trên mong muốn chúng tôi chỉ biết rằng phép tính () được tính toán và đắt.
stu

Câu trả lời:


181

Sau một số nghiên cứu và thử nghiệm, tôi tìm thấy giải pháp. Rõ ràng theo tiêu chuẩn [24.4.1 / 1] mối quan hệ giữa i.base () và i là:

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

(từ một bài báo của Tiến sĩ Dobbs ):

văn bản thay thế

Vì vậy, bạn cần phải áp dụng một bù khi lấy cơ sở (). Do đó, giải pháp là:

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

BIÊN TẬP

Cập nhật cho C ++ 11.

Reverse_iterator ikhông thay đổi:

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

Reverse_iterator ilà nâng cao:

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

Tôi thấy điều này rõ ràng hơn nhiều so với giải pháp trước đây của tôi. Sử dụng bất cứ điều gì bạn yêu cầu.


27
Bạn nên lưu ý thêm một chút về bài viết mà bạn đã trích dẫn - để có thể mang theo biểu thức m_CursorStack.erase( (++i).base())(người đàn ông, làm công cụ này với các trình vòng lặp ngược làm cho đầu tôi đau ...). Cũng cần lưu ý rằng bài viết DDJ được kết hợp vào cuốn sách "STL hiệu quả" của Meyer.
Michael Burr

8
Tôi thấy sơ đồ đó khó hiểu hơn là hữu ích. Vì rbegin, ri và render đều thực sự chỉ vào phần tử ở bên phải của những gì chúng được vẽ để chỉ vào. Biểu đồ cho thấy phần tử nào bạn sẽ truy cập nếu là bạn *, nhưng chúng ta đang nói về phần tử nào bạn sẽ chỉ vào nếu bạn baselà phần tử bên phải. Tôi không phải là một fan hâm mộ của các giải pháp --(i.base())hoặc (++i).base()vì chúng làm thay đổi trình vòng lặp. Tôi thích (i+1).base()cái nào hoạt động tốt.
mgiuca

4
Lặp lại đảo ngược là những kẻ nói dối .. khi được bảo vệ, một trình vòng lặp ngược trả về phần tử trước nó . Xem tại đây
bobobobo

4
Chỉ cần hoàn toàn rõ ràng, kỹ thuật này vẫn không thể được sử dụng trong một vòng lặp for bình thường (trong đó trình vòng lặp được tăng lên theo cách thông thường). Xem stackoverflow.com/questions/37005449/ từ
logidelic

1
m_CthonStack.erase ((++ i) .base ()) có vẻ như sẽ là một vấn đề nếu ++ tôi đưa bạn qua phần tử cuối cùng. bạn có thể gọi xóa trên end () không?
stu

16

Xin lưu ý rằng m_CursorStack.erase( (++i).base())có thể là một vấn đề nếu được sử dụng trong một forvòng lặp (xem câu hỏi ban đầu) vì nó thay đổi giá trị của i. Biểu thức đúng làm_CursorStack.erase((i+1).base())


4
Bạn cần tạo một bản sao của trình vòng lặp và thực hiện iterator j = i ; ++j, vì i+1không hoạt động trên trình vòng lặp, nhưng đó là ý tưởng đúng
bobobobo

3
@bobobobo, bạn có thể sử dụng m_CursorStack.erase(boost::next(i).base())với Boost. hoặc trong C ++ 11m_CursorStack.erase(std::next(i).base())
alfC

12

... Hoặc một cách khác để loại bỏ yếu tố này khỏi danh sách?

Điều này đòi hỏi -std=c++11cờ (cho auto):

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

Làm việc say mê :)
Den-Jason

@GaetanoMendola: tại sao?
slashmais

3
Ai đảm bảo cho bạn rằng các trình vòng lặp trong danh sách được sắp xếp?
Gaetano Mendola

7

Thật buồn cười là không có giải pháp chính xác trên trang này. Vì vậy, sau đây là đúng:

Trong trường hợp bộ lặp tiến, giải pháp là thẳng về phía trước:

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

Trong trường hợp lặp lại, bạn cần phải làm như vậy:

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

Ghi chú:

  • Bạn có thể xây dựng một reverse_iteratortừ một trình vòng lặp
  • Bạn có thể sử dụng giá trị trả về của std::list::erase

Mã này hoạt động, nhưng vui lòng giải thích lý do tại sao tiếp theo được sử dụng và làm thế nào an toàn để chuyển một trình vòng lặp tiến tới một trình vòng lặp ngược mà không làm thế giới sụp đổ
Lefteris E

1
@LefterisE Đó không phải là diễn viên. Nó tạo ra một trình vòng lặp ngược mới ra khỏi một interator. Đây là hàm tạo bình thường của trình vòng lặp ngược.
Šimon Tóth

3

Trong khi sử dụng reverse_iterator's base()phương pháp và decrementing kết quả làm việc ở đây, đó là đáng chú ý là reverse_iterators không cho tình trạng giống như thường xuyên iterators. Nói chung, bạn nên thích iterators thường xuyên hơn s reverse_iterator(cũng như const_iterators và const_reverse_iterators), vì những lý do chính xác như thế này. Xem Tạp chí của Bác sĩ Dobbs để thảo luận chuyên sâu về lý do tại sao.


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

Và đây là đoạn mã để chuyển đổi kết quả của việc xóa trở lại một trình vòng lặp ngược để xóa một phần tử trong một thùng chứa trong khi lặp lại theo chiều ngược lại. Hơi lạ một chút, nhưng nó hoạt động ngay cả khi xóa phần tử đầu tiên hoặc cuối cùng:

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

Nếu bạn không cần xóa mọi thứ khi bạn thực hiện, thì để giải quyết vấn đề, bạn có thể sử dụng thành ngữ erase-remove:

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

std::removehoán đổi tất cả các mục trong vùng chứa khớp pCursorđến cuối và trả về một trình vòng lặp cho mục khớp đầu tiên. Sau đó,erase sử dụng một phạm vi sẽ xóa khỏi trận đấu đầu tiên và đi đến kết thúc. Thứ tự của các yếu tố không phù hợp được bảo tồn.

Điều này có thể giải quyết nhanh hơn cho bạn nếu bạn đang sử dụng một std::vector , trong đó việc xóa ở giữa nội dung có thể liên quan đến việc sao chép hoặc di chuyển rất nhiều.

Hoặc tất nhiên, các câu trả lời ở trên giải thích việc sử dụng reverse_iterator::base()rất thú vị và đáng để biết, để giải quyết vấn đề chính xác đã nêu, tôi cho rằng đó std::removelà cách phù hợp hơn.


1

Chỉ muốn làm rõ điều gì đó: Trong một số ý kiến ​​và câu trả lời ở trên, phiên bản di động để xóa được đề cập là (++ i) .base (). Tuy nhiên, trừ khi tôi thiếu một cái gì đó, câu lệnh đúng là (++ ri) .base (), nghĩa là bạn 'gia tăng' Reverse_iterator (không phải là iterator).

Tôi đã gặp phải một nhu cầu để làm một cái gì đó tương tự ngày hôm qua và bài viết này là hữu ích. Cảm ơn mọi người.


0

Để bổ sung cho câu trả lời của người khác và vì tôi vấp phải câu hỏi này trong khi tìm kiếm về std :: string mà không có nhiều thành công, đây là câu trả lời với việc sử dụng std :: string, std :: string :: erase và std :: Reverse_iterator

Vấn đề của tôi là xóa tên tệp hình ảnh khỏi chuỗi tên tệp hoàn chỉnh. Ban đầu nó đã được giải quyết với std :: string :: find_last_of, nhưng tôi nghiên cứu một cách khác với 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\'

Điều này sử dụng thuật toán, iterator và tiêu đề chuỗi.


0

iterator ngược khá khó sử dụng. Vì vậy, chỉ sử dụng lặp chung. 'r' Nó bắt đầu từ yếu tố cuối cùng. Khi tìm thấy một cái gì đó để xóa. xóa nó và trở lại iterator tiếp theo. ví dụ: khi xóa phần tử thứ 3, nó sẽ trỏ phần tử thứ 4 hiện tại. và thứ 3 mới. Vì vậy, nên giảm 1 để di chuyển sang trái

void remchar(string& s,char c)
{      
    auto r = s.end() - 1;
    while (r >= s.begin() && *r == c)
    {
        r = s.erase(r);
        r -= 1;
    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.