Выбор определенных элементов из вектора
у меня есть вектор 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 можно делать большинство связанных с хранением вещей, но соответствующий выбор контейнеров часто дает значительные улучшения в производительности.