web-dev-qa-db-ja.com

copy_ifを間違って使用していますか?

私はVisual Studio 2010を使用していて、std :: copy_ifを使用しようとしています。述語を満たしているすべての値をコピーしたいと思います。例えば:

struct comp
{
    bool operator()(const int i) { return i == 5 || i == 7; }
};

int main()
{
    array<int, 10> arr =  { 3, 2, 5, 7, 3, 5, 6, 7 };
    vector<int> res;
    copy_if(arr.begin(), arr.end(), res.begin(), comp());

    for(int i = 0; i < res.size(); i++)
    {
        cout << res[i] << endl;
    }

    return 0;
}

しかし、このコードを実行すると、次のようになります。インクリメンタルではないベクトル反復子。

30
Merni

Copy_ifアルゴリズムは次のようになります(MSVC2010から取得)。

template<class InIt, class OutIt, class Pr> inline
OutIt copy_if(InIt First, InIt Last, OutIt Dest, Pr Pred)
{
    for (; First != _Last; ++First)
        if (Pred(*_First))
            *Dest++ = *First;
    return (Dest);
}

そして、copy_ifがPush_backを行わないことがわかるように、イテレータがある位置に値をコピーし、イテレータをインクリメントします。代わりに使用したいのはstd :: back_inserterで、これは要素をベクトルの後ろに押し戻します。 MSVC2010を使用している場合は、Microsoftが拡張機能として提供している関数オブジェクトの代わりにLambdaを使用できます(C++ 0x)

int main()
{
    array<int, 10> arr =  { 3, 2, 5, 7, 3, 5, 6, 7 };
    vector<int> res;
    copy_if(arr.begin(), arr.end(), back_inserter(res),[](const int i) { return i == 5 || i == 7; });

    for(unsigned i = 0; i < res.size(); i++)
        cout << res[i] << endl;

    return 0;
}
50
hidayat

パフォーマンスが問題になる場合は、std :: back_inserterを使用して宛先ベクトルにデータを設定する代わりに(コストのかかる任意の数の宛先ベクトルの再割り当てを伴うアプローチ)、ソースサイズの宛先ベクトルとdestを指定してstd :: copy_ifを呼び出してください。 erase(iteratorReturnedByCopyIf、dest.end())-最初の割り当てと、erase()の1つの再割り当てを含むアプローチ。

データ

C++ std::copy_if performance by dest-writing algorithm

コード

#include <algorithm>
#include <chrono>
#include <functional>
#include <iostream>
#include <iterator>
#include <numeric>
#include <vector>

long long MeasureMilliseconds(std::function<void()> func, unsigned iterations)
{
   auto beginTime = std::chrono::high_resolution_clock::now();
   for (unsigned i = 0; i < iterations; ++i)
   {
      func();
   }
   auto endTime = std::chrono::high_resolution_clock::now();
   long long milliseconds = std::chrono::duration_cast<
      std::chrono::milliseconds>(endTime - beginTime).count();
   return milliseconds;
}

bool IsEven(int i)
{
   return i % 2 == 0;
}

int main()
{
   const unsigned Iterations = 300000;
   for (size_t N = 0; N <= 100; N += 2)
   {
      std::vector<int> source(N);
      // Populate source with 1,2,...,N
      std::iota(std::begin(source), std::end(source), 1);

      long long backInserterMilliseconds = MeasureMilliseconds([&]
      {
         std::vector<int> dest;
         std::copy_if(std::begin(source), std::end(source), 
            std::back_inserter(dest), IsEven);
      }, Iterations);

      long long sourceSizeAndEraseMilliseconds = MeasureMilliseconds([&]
      {
         std::vector<int> dest(source.size());
         std::vector<int>::iterator copyIfIterator = std::copy_if(
            std::begin(source), std::end(source), std::begin(dest), IsEven);
         dest.erase(copyIfIterator, dest.end());
      }, Iterations);

      std::cout << "N=" << N << '\n';
      std::cout << "Default-size dest and back_inserter: " << 
         backInserterMilliseconds << '\n';
      std::cout << "      Source-sized dest and erase(): " << 
         sourceSizeAndEraseMilliseconds << "\n\n";
   }
   return 0;
}

コード出力

N=90
Default-size dest and back_inserter: 469
      Source-sized dest and erase(): 89

N=92
Default-size dest and back_inserter: 472
      Source-sized dest and erase(): 90

N=94
Default-size dest and back_inserter: 469
      Source-sized dest and erase(): 92

N=96
Default-size dest and back_inserter: 478
      Source-sized dest and erase(): 92

N=98
Default-size dest and back_inserter: 471
      Source-sized dest and erase(): 93

N=100
Default-size dest and back_inserter: 480
      Source-sized dest and erase(): 92

参考文献

[alg.copy]
Qt ScatterChart

16
Neil Justice

出力反復子を使用できます。

copy_if(arr.begin(), arr.end(), std::back_inserter(res), comp());
14
Björn Pollex

配列サイズを予約します。 hidayat これの理由を示します。

res.resize(arr.size());
6
dubnde