从矢量中选择特定元素
Select specific elements from a vector
我有一个向量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之内
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
与 lambda 一起使用,即"擦除-删除-习语":
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
元素。但是,文档中的语言并未明确保证这一点。它只是说:
删除是通过移动(通过移动分配)区域中的元素来完成的,以使不删除的元素出现在范围的开头。保留剩余元素的相对顺序,容器的物理大小保持不变。指向范围的新逻辑端和物理端之间的元素的迭代器仍然是可取消引用的,但元素本身具有未指定的值(根据 MoveAssignable 后置条件)。对删除的调用通常后跟对容器的 erase 方法的调用,该方法将擦除未指定的值并减小容器的物理大小以匹配其新的逻辑大小。
现在,仅使用std::vector
的前向迭代器很难保证结果的稳定性,并且不按顺序应用谓词。但这样做肯定是可能的。
基于 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
的内容。
我听说你喜欢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 将索引元素访问者转换为元素访问者。 有点像拉姆达柔道。
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];
}
);
解决您的问题(现场示例)。
我实际上非常喜欢你这样做的方式,但我会在限制使用临时向量的范围方面进行一些更改,我会使用 std::vector::swap 来避免在最后复制。如果你有C++11
你可以使用 std::move 而不是 std
#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';
}
不同的版本可以就地擦除元素,但不需要像伊戈尔算法那样多的移动,并且在要擦除少量元素的情况下可能更有效:
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
(或 C++11 的forward_list
)而不是vector
,则可以就地执行此操作,而无需vector
操作所需的移动/分配/复制开销。 使用任何 STL 容器完全可以执行大多数与存储相关的事情,但适当选择容器通常会显著提高性能。
- 在C++中,如何通过几种类型从元组中选择多个元素
- 高级选择排序 - 在一次迭代中搜索两个元素
- 选择一个元素而不是一个对象的数组的原因
- 为什么这个选择排序算法仍然切换一个元素,当它已经是其他元素中最小的元素时?
- 快速排序 - 三个中位数枢轴选择 - 某些元素顺序不正确
- 从C++数组中选择一个随机元素
- 从加密项目向量中解密任意选择的元素会导致无效的 PKCS #7 块错误
- 从 std::vector 中选择一个元素,而不是给定元素
- 如何使用cpp编写选择排序算法以降序对元素列表进行排序?
- 从长(且合理)稀疏向量中选择随机元素的最有效方法是什么?
- 我的选择排序代码是否存在导致它跳过数组中的元素的问题?
- 使用 C++ 从每个循环的数组中选择 n 个元素
- 生成所有可能的元素选择,每个元素都来自不同的集合
- 选择元素的所有组合
- 如何使用递归从集合中选择所有可能的元素组合
- C++ 随机选择标准::矢量<标准::矢量的非空元素> >
- 我如何在C 中制作算法,以在不重复的情况下查找集合的变化(即n元素,选择k)
- 当中间元素选择为枢轴时,快速排序C 不起作用
- 选择C++地图中随机元素的百分比
- 为什么在指针上对成员访问/元素选择有不同的运算符