web-dev-qa-db-ja.com

ベクトルから特定の要素を選択する

ベクター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;

それを行うより良い方法はありますか?

  • c ++ 03
  • c ++ 11
22
user31264
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の再実装です(使用する関数オブジェクトには、コンテナーへのインデックスやイテレーターではなく値が与えられるため、直接使用するのは困難です)。

20
Igor Tandetnik

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_ifv1の要素に述語を順番に適用することですただし、ドキュメントはこれを明示的に保証していません。それは単に states

削除は、削除する要素が範囲の先頭に表示されるように、範囲内の要素を(移動割り当てによって)シフトすることによって行われます。残っている要素の相対的な順序は保持され、コンテナの物理的なサイズは変更されません。新しい論理端と範囲の物理端の間の要素を指すイテレータは引き続き参照不可ですが、要素自体には未指定の値があります(MoveAssignable事後条件に従って)。通常、removeの呼び出しの後に、コンテナーのeraseメソッドが呼び出されます。このメソッドは、指定されていない値を消去し、コンテナーの物理サイズを減らして、新しい論理サイズに一致させます。

さて、std::vectorへのフォワードイテレータだけでは、結果の安定性を保証し、述語を順序どおりに適用することは困難です。しかし、そうすることは確かに可能です

18
aruisdante

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 のようなものを使用することもできます。

7
manlio

ラムダが好きだと聞きました。

_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++11std :: 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';
}
3
Galik

所定の位置にある要素を消去するが、イゴールのアルゴが必要とするほど多くの移動を必要とせず、消去する要素が少量の場合はより効率的な別のバージョン:

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());

しかし、このアルゴは不安定です。

2
Slava

list(またはforward_list for C++ 11)vectorの代わりに、vector操作に必要な移動/割り当て/コピーのオーバーヘッドなしで、これをインプレースで実行できます。ほとんどのストレージ関連の作業を任意のSTLコンテナーで実行することは完全に可能ですが、コンテナーを適切に選択すると、パフォーマンスが大幅に向上することがよくあります。

1
Graham