Отслеживание удаленных элементов с использованием std :: remove_if - PullRequest
5 голосов
/ 05 марта 2012

Я хочу удалить некоторые элементы из vector и использую алгоритм remove_if для этого. Но я хочу отслеживать удаленные элементы, чтобы потом можно было выполнить с ними некоторые операции. Я попробовал это с помощью следующего кода:

#include <vector>
#include <algorithm>
#include <iostream>

using namespace std;


struct IsEven
{
    bool operator()(int n) 
    {
        if(n % 2 == 0)
        {
            evens.push_back(n);
            return true;
        }

        return false;
    }

    vector<int> evens;
};

int main(int argc, char **argv)
{

    vector<int> v;
    for(int i = 0; i < 10; ++i)
    {
        v.push_back(i);
    }

    IsEven f;
    vector<int>::iterator newEnd = remove_if(v.begin(), v.end(), f);
    for(vector<int>::iterator it = f.evens.begin(); it != f.evens.end(); ++it)
    {
        cout<<*it<<"\n";
    }

    v.erase(newEnd, v.end());

    return 0;
}

Но это не работает, поскольку remove_if принимает копию моего объекта-функтора, поэтому сохраненный вектор evens недоступен. Как правильно достичь этого?

P.S. : Пример с четными и коэффициентами только для примера, мой реальный код чем-то отличается. Поэтому не предлагайте способ идентифицировать четные или нечетные значения по-другому.

Ответы [ 6 ]

9 голосов
/ 05 марта 2012

Решение не remove_if, а двоюродный брат part_sort partition. Разница в том, что remove_if только гарантирует, что [begin, middle) содержит совпадающие элементы, но partition также гарантирует, что [middle, end) содержит элементы, которые не соответствуют предикату.

Итак, ваш пример становится просто (обратите внимание, что evens больше не нужен):

vector<int>::iterator newEnd = partition(v.begin(), v.end(), f);
for(vector<int>::iterator it = newEnd; it != v.end(); ++it)
{
    cout<<*it<<"\n";
}
v.erase(newEnd, v.end());
3 голосов
/ 05 марта 2012

Ваша лучшая ставка - std::partition(), которая переставит всех эльтов в последовательности, таких как все эльты, для которых ваш предикатный возврат true будет предшествовать тем, для которых он возвращает ложь.

Пример:

vector<int>::iterator bound = partition (v.begin(), v.end(), IsEven);
std::cout << "Even numbers:" << std::endl;
for (vector<int>::iterator it = v.begin(); it != bound; ++it)
  std::cout << *it << " ";

std::cout << "Odd numbers:" << std::endl;
for (vector<int>::iterator it = bound; it != v.end(); ++it)
  std::cout << *it << " ";
2 голосов
/ 05 марта 2012

Вы можете избежать копирования вашего функтора (т.е. передать по значению), если вы передадите ist по reference , например так:

vector<int>::iterator newEnd = remove_if(v.begin(), v.end(), 
   boost::bind<int>(boost::ref(f), _1));

Если вы не можете использовать boost, то же самое возможно с std::ref. Я протестировал приведенный выше код, и он работает как положено.

1 голос
/ 05 марта 2012

Дополнительный уровень косвенности.Объявите вектор локально, и IsEven будет содержать копию для него.Для IsEven также возможно владение вектором, при условии, что он динамически распределяется и управляется shared_ptr.На практике я обычно находил решение с локальной переменной и указателем более удобным.Что-то вроде:

class IsEven
{
    std::vector<int>* myEliminated;
public:
    IsEven( std::vector<int>* eliminated = NULL )
        : myEliminated( eliminated )
    {
    }
    bool
    operator()( int n ) const
    {
        bool results = n % 2 == 0;
        if ( results && myEliminated != NULL ) {
            myEliminated->push_back( n );
        }
        return results;
    }
}

Обратите внимание, что это также позволяет функции operator()() быть const.Я думаю, что это формально необходимо (хотя я не уверен).

0 голосов
/ 05 марта 2012

У вас может быть другое решение; только если вам не нужно удалять эльтов одновременно (не так ли?). С std::for_each(), который возвращает копию вашего функтора. Exemple:

IsEven result = std::for_each(v.begin(), v.end(), IsEven());

// Display the even numbers.
std::copy(result.evens.begin(), result.evens.end(), std::ostream_iterator<int> (cout, "\n"));  

Обратите внимание, что всегда лучше создавать безымянные переменные в c ++, когда это возможно. Здесь это решение не совсем отвечает вашей основной проблеме (удаление elts из исходного контейнера), но напоминает всем, что std :: for_each () возвращает копию вашего функтора. : -)

0 голосов
/ 05 марта 2012

Проблема, которую я вижу в коде, заключается в том, что вектор evens, который вы создаете внутри структуры, создается каждый раз, когда его вызывает алгоритм remove_if. Поэтому независимо от того, передадите ли вы функтор в remove_if, он будет каждый раз создавать новый вектор. Таким образом, как только последний элемент удален и когда вызов функции заканчивается и выходит из функции, f.evens всегда будет извлекать пустой вектор. Это можно отсортировать двумя способами:

  1. Заменить структуру классом и объявить четные как статические (если это то, что вы хотели)
  2. Или вы можете сделать глобальные события. Я бы лично не рекомендовал это (это делает код плохим, скажите глобальным, если они вам действительно не нужны).

Edit:

В соответствии с предложением Набульке, вы также можете использовать std :: ref, например, std :: ref (f). Это препятствует тому, чтобы вы сделали вектор глобальным, и избавляет от ненужной статики.

Пример того, как сделать его глобальным, выглядит следующим образом:

#include <vector>
#include <algorithm>
#include <iostream>

using namespace std;
vector<int> evens;

struct IsEven
{
    bool operator()(int n) 
    {
        if(n % 2 == 0)
        {
            evens.push_back(n);
            return true;
        }

        return false;
    }


};

int main(int argc, char **argv)
{

    vector<int> v;
    for(int i = 0; i < 10; ++i)
    {
        v.push_back(i);
    }

    IsEven f;
    vector<int>::iterator newEnd = remove_if(v.begin(), v.end(), f);
    for(vector<int>::iterator it = evens.begin(); it != evens.end(); ++it)
    {
        cout<<*it<<"\n";
    }

    v.erase(newEnd, v.end());

    return 0;
}

Мне кажется, этот код работает нормально. Дайте мне знать, если это не то, что вы хотели.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...