ベクターv1
と、同じサイズのブールベクターv2
があります。 v1
の並列要素がfalse
になるようなすべての値をv2
から削除したい:
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;
それを行うより良い方法はありますか?
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
を使用できます。ラムダは "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
の要素に述語を順番に適用することです。ただし、ドキュメントはこれを明示的に保証していません。それは単に states :
削除は、削除する要素が範囲の先頭に表示されるように、範囲内の要素を(移動割り当てによって)シフトすることによって行われます。残っている要素の相対的な順序は保持され、コンテナの物理的なサイズは変更されません。新しい論理端と範囲の物理端の間の要素を指すイテレータは引き続き参照不可ですが、要素自体には未指定の値があります(MoveAssignable事後条件に従って)。通常、removeの呼び出しの後に、コンテナーの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
のようなものを使用することもできます。
ラムダが好きだと聞きました。
_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)
_型のラムダを返します。返されたラムダは、インデックス付き要素ビジターを要素ビジターに変換します。ラムダ柔道の一種。
_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];
}
));
_
削除/消去での移動の制限shouldは、元の位置にある要素のラムダを呼び出すことを意味します。
より基本的なステップでこれを行うことができます。途中で複雑になります...
まず、小さな名前付き演算子ライブラリ:
_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 :: vector :: swap の代わりに std :: move を使用できます:
#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
(またはforward_list
for C++ 11)vector
の代わりに、vector
操作に必要な移動/割り当て/コピーのオーバーヘッドなしで、これをインプレースで実行できます。ほとんどのストレージ関連の作業を任意のSTLコンテナーで実行することは完全に可能ですが、コンテナーを適切に選択すると、パフォーマンスが大幅に向上することがよくあります。