Я неправильно использую 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;
}

Но когда я запускаю этот код, я получаю: векторный итератор не увеличивается.


person Merni    schedule 25.03.2011    source источник


Ответы (4)


Алгоритм 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, вы можете использовать Lambda вместо объекта функции, который Microsoft предлагает в качестве расширения (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;
}
person hidayat    schedule 25.03.2011
comment
Хотя этот ответ приводит к правильному выводу, объяснение довольно запутанно. Ошибка не имеет ничего общего с пустым вектором. Алгоритм просто ожидает итератор, удовлетворяющий концепции выходного итератора. Кроме того, в вашем последнем примере кода вы используете лямбда-выражение для предиката, которое не поддерживается в текущем стандарте С++ (и в ОП не упоминается С++0x). - person Björn Pollex; 25.03.2011
comment
Но copy_if также не является частью текущего стандарта, но и copy_if, и lambda являются частью недавнего проекта C++0x. - person hidayat; 25.03.2011
comment
@hidyat: Однако вы правы, Microsoft предлагает его как пользовательское расширение. Если ОП не помечает свой вопрос C++0x, вы должны хотя бы упомянуть, что это ваш ответ. - person Björn Pollex; 25.03.2011
comment
Мне кажется немного странным называть C++0x или Lambdas пользовательским расширением. В них нет ничего привычного. Начиная со следующего года (когда, надеюсь, будет выпущен новый стандарт) любой компилятор, который его не поддерживает, будет считаться несоответствующим. - person Fabio Fracassi; 25.03.2011
comment
@Fabio: Custom может быть плохим словом. @hidayat: Пожалуйста, забудьте мой первый комментарий, он неправильный (как объяснил мне @visitor). - person Björn Pollex; 25.03.2011

Если производительность вызывает беспокойство, рассмотрите вместо использования std::back_inserter для заполнения вектора назначения (подход, который включает произвольное количество дорогостоящих перераспределений вектора назначения), вызовите std::copy_if с исходным вектором назначения, за которым следует dest. Erase(iteratorReturnedByCopyIf, dest.end()) — подход, который включает одно предварительное выделение, а затем одно перераспределение для Erase().

Данные

Производительность C++ std::copy_if по алгоритму целевой записи

Код

#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

person Neil Justice    schedule 18.09.2016
comment
Как изменится тест для std::back_inserter, если dest.reserve(source.size()) будет вызван до вызова copy_if? - person OnlineCop; 28.08.2018
comment
На моей машине, использующей Visual Studio 2017 - Visual C++ 14.1, dest.reserve(source.size()) на самом деле немного быстрее... - person Teris; 06.10.2019

Вы можете использовать итератор вывода:

copy_if(arr.begin(), arr.end(), std::back_inserter(res), comp());
person Björn Pollex    schedule 25.03.2011
comment
Хотя ваш ответ приводит к правильному выводу, я нахожу ваше объяснение довольно запутанным. Вам не нужно вставлять новые элементы в вектор, и вы можете использовать copy_if для перезаписи существующих элементов, если размер вектора достаточен. - person visitor; 25.03.2011

Зарезервируйте размер массива. hidayat объясняет причину этого.

res.resize(arr.size());
person dubnde    schedule 25.03.2011
comment
@space: В некотором смысле изменение размера помогает. Итератор Vector хорошо работает как итератор вывода (вы можете сделать *it = n; хорошо, если it указывает на допустимое местоположение). Например, вы можете сделать это следующим образом, используя возвращаемое значение для удаления лишних элементов: ideone.com/QKnry . Или вы можете использовать count_if, чтобы определить, насколько большим должен быть результирующий вектор. Хорошие ли это способы — другой вопрос. - person visitor; 25.03.2011
comment
Как объяснил @visitor, ваш ответ действительно правильный. Однако я не могу удалить отрицательный голос, если вы не отредактируете свой ответ. - person Björn Pollex; 25.03.2011
comment
@Space_C0wb0y Я сделал небольшое редактирование, чтобы вы могли проголосовать за - person dubnde; 25.03.2011
comment
Вам также нужно будет обрезать лишние элементы после копирования правильных, возможно, на res.erase(copy_if(...),res.end()). На мой взгляд, было бы проще использовать back_inserter. - person Mike Seymour; 25.03.2011