Удаление элементов из списка STL
Я хочу создать функцию, которая перемещает элементы из одного списка STL в другой, если они соответствуют определенному условию.
Этот код не способ сделать это. Итератор скорее всего будет недействителен функцией erase() и вызовет проблему:
for(std::list<MyClass>::iterator it = myList.begin(); it != myList.end(); it++)
{
if(myCondition(*it))
{
myOtherList.push_back(*it);
myList.erase(it);
}
}
Так может ли кто-нибудь предложить лучший способ сделать это?
Ответы
Ответ 1
Erase
возвращает итератор, указывающий на элемент после стираемого:
std::list<MyClass>::iterator it = myList.begin();
while (it != myList.end())
{
if(myCondition(*it))
{
myOtherList.push_back(*it);
it = myList.erase(it);
}
else
{
++it;
}
}
Ответ 2
В списках STL есть интересная функция: метод splice()
позволяет разрушать элементы из одного списка в другой.
splice()
работает в постоянное время и не копирует элементы или не выполняет никаких бесплатных распределений/деаллокаций. Обратите внимание, что оба списка должны быть одного типа, и они должны быть отдельными экземплярами списка (не две ссылки на один и тот же список).
Вот пример того, как вы могли бы использовать splice()
:
for(std::list<MyClass>::iterator it = myList.begin(); it != myList.end(); ) {
if(myCondition(*it)) {
std::list<MyClass>::iterator oldIt = it++;
myOtherList.splice(myOtherList.end(), myList, oldIt);
} else {
++it;
}
}
Ответ 3
Решение 1
template<typename Fwd, typename Out, typename Operation>
Fwd move_if(Fwd first, Fwd last, Out result, Operation op)
{
Fwd swap_pos = first;
for( ; first != last; ++first ) {
if( !op(*first) ) *swap_pos++ = *first;
else *result++ = *first;
}
return swap_pos;
}
Идея проста. Вы хотите удалить элементы из одного контейнера и поместить их в другой, если предикат является истинным. Поэтому возьмите код алгоритма std::remove()
, который уже выполняет удаление части, и адаптируйте ее к вашим дополнительным потребностям. В приведенном выше коде я добавил строку else
, чтобы скопировать элемент, когда предикат является истинным.
Обратите внимание, что, поскольку мы используем код std::remove()
, алгоритм фактически не сокращает входной контейнер. Однако он возвращает обновленный конечный итератор входного контейнера, поэтому вы можете просто использовать это и игнорировать дополнительные элементы. Используйте erase-remove idiom, если вы действительно хотите сжать контейнер ввода.
Решение 2
template<typename Bidi, typename Out, typename Operation>
Bidi move_if(Bidi first, Bidi last, Out result, Operation op)
{
Bidi new_end = partition(first, last, not1(op));
copy(new_end, last, result);
return new_end;
}
Второй подход использует STL для реализации алгоритма. Я лично считаю его более читаемым, чем первое решение, но имеет два недостатка: во-первых, для более мощных двунаправленных итераторов для контейнера ввода, а не для форвардных итераторов, которые мы использовали в первом решении. Во-вторых, и это может быть или не быть проблемой для вас, у контейнеров не гарантируется тот же порядок, что и до вызова std::partition()
. Если вы хотите сохранить заказ, замените этот вызов на вызов std::stable_partition()
. std::stable_partition()
может быть немного медленнее, но он имеет ту же самую сложность выполнения, что и std::partition()
.
Любой способ: вызов функции
list<int>::iterator p = move_if(l1.begin(), l1.end(),
back_inserter(l2),
bind2nd(less<int>(), 3));
Заключительные замечания
При написании кода я столкнулся с дилеммой: что должен вернуть алгоритм move_if()
? С одной стороны, алгоритм должен возвращать итератор, указывающий на новую конечную позицию входного контейнера, поэтому вызывающий может использовать стирание-удаление идиомы для сжатия контейнера. Но, с другой стороны, алгоритм должен возвращать позицию конца контейнера результата, потому что в противном случае вызывающему может оказаться дорогостоящим. В первом решении итератор result
указывает на эту позицию, когда алгоритм заканчивается, а во втором - итератором, возвращаемым std::copy()
, который указывает на эту позицию. Я мог бы вернуть пару итераторов, но для упрощения дела я просто возвращаю один из итераторов.
Ответ 4
std::list<MyClass>::iterator endMatching =
partition(myList.begin(), myList.end(), myCondition);
myOtherList.splice(myOtherList.begin(), myList, endMatching, myList.end());
Обратите внимание, что partition() дает вам достаточно, чтобы различать совпадающие объекты от несовпадающих.
(list:: splice() дешево, однако)
См. следующий код в конкретном случае, вдохновленном
Теперь удалить элементы, соответствующие предикату?
#include <iostream>
#include <iterator>
#include <list>
#include <string>
#include <algorithm>
#include <functional>
using namespace std;
class CPred : public unary_function<string, bool>
{
public:
CPred(const string& arString)
:mString(arString)
{
}
bool operator()(const string& arString) const
{
return (arString.find(mString) == std::string::npos);
}
private:
string mString;
};
int main()
{
list<string> Strings;
Strings.push_back("213");
Strings.push_back("145");
Strings.push_back("ABC");
Strings.push_back("167");
Strings.push_back("DEF");
cout << "Original list" << endl;
copy(Strings.begin(), Strings.end(),ostream_iterator<string>(cout,"\n"));
CPred Pred("1");
// Linear. Exactly last - first applications of pred, and at most (last - first)/2 swaps.
list<string>::iterator end1 =
partition(Strings.begin(), Strings.end(), Pred);
list<string> NotMatching;
// This function is constant time.
NotMatching.splice(NotMatching.begin(),Strings, Strings.begin(), end1);
cout << "Elements matching with 1" << endl;
copy(Strings.begin(), Strings.end(), ostream_iterator<string>(cout,"\n"));
cout << "Elements not matching with 1" << endl;
copy(NotMatching.begin(), NotMatching.end(), ostream_iterator<string>(cout,"\n"));
return 0;
}
Ответ 5
Другая попытка:
for(std::list<MyClass>::iterator it = myList.begin(); it != myList.end; ) {
std::list<MyClass>::iterator eraseiter = it;
++it;
if(myCondition(*eraseiter)) {
myOtherList.push_back(*eraseiter);
myList.erase(eraseiter);
}
}
Ответ 6
template <typename ForwardIterator, typename OutputIterator, typename Predicate>
void splice_if(ForwardIterator begin, ForwardIterator end, OutputIterator out, Predicate pred)
{
ForwardIterator it = begin;
while( it != end )
{
if( pred(*it) )
{
*begin++ = *out++ = *it;
}
++it;
}
return begin;
}
myList.erase(
splice_if( myList.begin(), myList.end(), back_inserter(myOutputList),
myCondition
),
myList.end()
)