Выбор определенных элементов из вектора

у меня есть вектор v1, и логический вектор v2 такого же размера. Я хочу удалить из v1 все значения такие, что параллельный элемент v2 и false:

vector<int> v3; // assume v1 is vector<int>
for (size_t i=0; i<v1.size(); i++)
    if (v2[i])
        v3.push_back(v1[i]);
v1=v3;

есть ли лучший способ сделать это?

  • в C++03
  • в C++11

7 ответов


size_t last = 0;
for (size_t i = 0; i < v1.size(); i++) {
  if (v2[i]) {
    v1[last++] = v1[i];
  }
}
v1.erase(v1.begin() + last, v1.end());

то же, что и у вас по существу, за исключением того, что он работает на месте, не требуя дополнительного хранения. Это в основном перевыполнение std::remove_if (что было бы трудно использовать, потому что объект функции, он использует это значение, а не указатель или итератор в контейнер).


в C++11 вы можете использовать std::remove_if и std::erase с лямбдой, которая является "стереть-удалить-фразеологизм":

size_t idx = 0;
v1.erase(std::remove_if(v1.begin(),
                          v1.end(),
                          [&idx, &v2](int val){return !v2[idx++];}),
           v1.end())

и вот ссылка на него, функционирующая по назначению: cpp.sh / 57jpc

как отмечают комментарии, однако, есть немного обсуждения о безопасности этого способа; основное предположение здесь заключается в том, что std::remove_if будет применять предикат к элементам v1 в порядке. однако, язык в doc явно не гарантирует этого. Это просто государства:

удаление осуществляется путем сдвига (путем присваивания перемещения) элементов в диапазоне таким образом, что элементы, которые не должны быть удалены появляются в начале диапазона. Относительный порядок оставшихся элементов сохраняется, а физический размер контейнера не изменяется. Итераторы, указывающие на элемент между новым логическим концом и физическим концом диапазона еще поддерживать удаление ссылок, а сами элементы имеют неопределенные значения, (в соответствии с MoveAssignable постусловие). Вызов remove обычно сопровождается вызовом метода erase контейнера, который стирает неопределенные значения и уменьшает физический размер контейнера в соответствии с его новым логическим размером.

теперь было бы трудно только с прямым итератором до std::vector чтобы гарантировать стабильность результатов и не применять предикат по порядку. Но это конечно возможно чтобы сделать так.


A remove_ifна основе альтернативы:

v1.erase(std::remove_if(v1.begin(), v1.end(),
                        [&v1, &v2](const int &x){ return !v2[&x - &v1[0]]; }),
         v1.end());

также учтите, что если вам нужен только вид на v1 в котором некоторые элементы пропущены, вы можете избежать изменения v1 и использовать что-то вроде boost::filter_iterator.


мне на самом деле очень нравится, как вы это сделали, но я бы сделал пару изменений в ограничении области временного вектора, и я бы использовал std::vector:: swap чтобы избежать копирования в конце. Если у вас есть C++11 можно использовать std:: move вместо std::vector:: swap:

#include <vector>
#include <iostream>

int main()
{
    std::vector<int> iv = {0, 1, 2, 3, 4, 5, 6};
    std::vector<bool> bv = {true, true, false, true, false, false, true};

    // start a new scope to limit
    // the lifespan of the temporary vector
    {
        std::vector<int> v;

        // reserve space for performance gains
        // if you don't mind an over-allocated return
        // v.reserve(iv); 

        for(std::size_t i = 0; i < iv.size(); ++i)
            if(bv[i])
                v.push_back(iv[i]);

        iv.swap(v); // faster than a copy
    }

    for(auto i: iv)
        std::cout << i << ' ';
    std::cout << '\n';
}

другая версия, которая стирает элементы на месте, но не требует столько ходов, сколько требует algo Игоря, и в случае небольшого количества элементов для стирания может быть более эффективной:

using std::swap;
size_t last = v1.size();
for (size_t i = 0; i < last;) {
   if( !v2[i] ) {
       --last;
       swap( v2[i], v2[last] );
       swap( v1[i], v1[last] );
   } else 
       ++i;
}
v1.erase(v1.begin() + last, v1.end());

но этот алгоритм является неустойчивым.


если вы используете list (или forward_list для C++11) вместо a vector, вы можете сделать это на месте без перемещения/выделения/копирования затраты, необходимые для vector операции. С любым контейнером STL можно делать большинство связанных с хранением вещей, но соответствующий выбор контейнеров часто дает значительные улучшения в производительности.