我有一个向量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;
有更好的方法吗?
答案 0 :(得分:20)
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
的重新实现(难以直接使用,因为它使用的函数对象被赋予一个值,而不是容器中的索引或迭代器)。
答案 1 :(得分:17)
在C ++ 11中,您可以将std::remove_if
和std::erase
与lambda一起使用,即"erase-remove-idiom":
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中的语言并未明确保证这一点。它只是states:
通过移动(通过移动分配)范围内的元素来完成移除,使得不被移除的元素出现在范围的开头。保留的元素的相对顺序被保留,容器的物理大小不变。指向新逻辑结束和范围的物理结束之间的元素的迭代器仍然是可解除引用的,但元素本身具有未指定的值(根据MoveAssignable后置条件)。调用remove之后通常会调用容器的erase方法,该方法会擦除未指定的值并减小容器的物理大小以匹配其新的逻辑大小。
现在,只有std::vector
的前向迭代器才能保证结果的稳定性并且不按顺序应用谓词。但这肯定是可能的。
答案 2 :(得分:7)
基于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
的内容。
答案 3 :(得分:7)
我听说你喜欢lambdas。
auto with_index_into = [](auto&v){
return [&](auto&& f){
return [&,f=decltype(f)(f)](auto& e){
return f( std::addressof(e)-v.data(), e );
};
};
};
这可能很有用。它需要一个.data()
支持容器,然后返回一个类型为((Index,E&)->X)->(E&->X)
的lambda - 返回的lambda将索引元素访问者转换为元素访问者。 lambda柔道。
template<class C, class Test>
auto erase_if( C& c, Test&& test) {
using std::begin; using std::end;
auto it=std::remove_if(begin(c),end(c),test);
if (it==end(c)) return false;
c.erase(it, end(c));
return true;
}
因为我讨厌在客户端代码中删除擦除习惯用法。
现在代码非常漂亮:
erase_if( v1, with_index_into(v1)(
[](std::size_t i, auto&e){
return !v2[i];
}
));
对删除/擦除中移动的限制意味着它会调用元素在其原始位置的lambda。
我们可以通过更多元素步骤来实现这一目标。它在中间变得复杂......
首先,微小的命名运算符库:
namespace named_operator {
template<class D>struct make_operator{};
enum class lhs_token {
star = '*',
non_char_tokens_start = (unsigned char)-1,
arrow_star,
};
template<class T, lhs_token, class O> struct half_apply { T&& lhs; };
template<class Lhs, class Op>
half_apply<Lhs, lhs_token::star, Op>
operator*( Lhs&& lhs, make_operator<Op> ) {
return {std::forward<Lhs>(lhs)};
}
template<class Lhs, class Op>
half_apply<Lhs, lhs_token::arrow_star, Op>
operator->*( Lhs&& lhs, make_operator<Op> ) {
return {std::forward<Lhs>(lhs)};
}
template<class Lhs, class Op, class Rhs>
auto operator*( half_apply<Lhs, lhs_token::star, Op>&& lhs, Rhs&& rhs )
{
return named_invoke( std::forward<Lhs>(lhs.lhs), Op{}, std::forward<Rhs>(rhs) );
}
template<class Lhs, class Op, class Rhs>
auto operator*( half_apply<Lhs, lhs_token::arrow_star, Op>&& lhs, Rhs&& rhs )
{
return named_next( std::forward<Lhs>(lhs.lhs), Op{}, std::forward<Rhs>(rhs) );
}
}
现在我们定义then
:
namespace lambda_then {
struct then_t:named_operator::make_operator<then_t> {} then;
template<class Lhs, class Rhs>
auto named_next( Lhs&& lhs, then_t, Rhs&& rhs ) {
return
[lhs=std::forward<Lhs>(lhs), rhs=std::forward<Rhs>(rhs)]
(auto&&...args)->decltype(auto)
{
return rhs( lhs( decltype(args)(args)... ) );
};
}
}
using lambda_then::then;
定义了一个标记then
,使得lambda1 ->*then* lambda2
返回一个接受其参数的函数对象,将其传递给lambda1,然后将返回值传递给lambda2。
接下来我们定义to_index(container)
:
template<class C>
auto index_in( C& c ) {
return [&](auto& e){
return std::addressof(e)-c.data();
};
}
我们还保留上述erase_if
。
这导致:
erase_if( v1,
index_in(v1)
->*then*
[&](auto i){
return !v2[i];
}
);
答案 4 :(得分:3)
我实际上非常喜欢你这样做但我会在限制使用临时矢量的范围方面做一些改变,我会使用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';
}
答案 5 :(得分:2)
不同的版本删除了元素,但不需要像Igor的算法那样多的移动,并且在少量元素被擦除的情况下可能更有效:
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());
但这个算法不稳定。
答案 6 :(得分:1)
如果使用list
(或forward_list
代替C ++ 11)而不是vector
,则可以就地执行此操作,而无需移动/分配/复制开销用于vector
次操作。使用任何STL容器完成大多数与存储相关的事情是完全可能的,但适当选择容器通常会显着提高性能。