C++ 使用copy_if获得数组vector掩膜

假设给定一个数组vector<double> veca以及对应的掩膜(即指示标志数组)vector<bool> flags,获得veca中对应flags中为true的元素。

假设veca{0.1, 0.2, 0.3, 0.4}flags{true, false, false, true},则vecb应该为{0.1, 0.4}

使用for循环自然可以很简单地解决这个问题,但是想要用标准模板库中的算法实现需要使用copy_if。使用copy算法的复制拷贝效率要比for的效率要高一些。

copy_if的由四个参数,前两个是输入元素的迭代器,拷贝两个迭代器之间的元素,第三个将元素拷贝到的位置,第四个是选择条件,即只拷贝改条件返回true的元素。

#include <vector>
#include <algorithm>
#include <iostream>
#include <numeric>    // accumulate
#include <iterator>

int main()
{
        std::vector<bool> flags{ true, true, false, true};
        std::vector<double> veca{ 0.1, 0.2, 0.3, -0.1 };
        
        std::vector<double> vecb;
        vecb.reserve(std::accumulate(flags.begin(), flags.end(), 0));
        
        // vecb在reserve之后并没有未元素分配内存,插入应该使用back_inserter(vecb)
        // vecb在resize之后为元素分配了内存,使用back_inserter(vecb)会在已经分配内存的元素之后插入
        // 这时应该使用 vecb.begin(),对已经分配的内存进行覆盖
        size_t i = 0;
        std::copy_if(veca.begin(), veca.end(), std::back_inserter(vecb),
                [&i, &flags](double a){return flags[i++]; });
        
        for (auto &s : vecb)
                std::cout << s << std::endl;

//      i = 0;
//      std::vector<double> vecc;
//      std::remove_copy_if(veca.begin(), veca.end(), std::back_inserter(vecc),
//              [&i, &flags](double a){return flags[i++]; });
//      std::cout << "veca\n";
//      for (auto& s : veca)
//              std::cout << s << "\t";
//      std::cout << "\nvecc\n";
//      for (auto& s : vecc)
//              std::cout << s << "\t";
        return 0;
}

使用lambda表达式作为第四个元素,捕获掩膜数组flags和元素序号i

注意lambda表达式中flagsi都是使用的引用捕获,对于数组等数据结构要使用引用捕获,而且我们希望在lambda表达式中改变i的值,因此i也要是用引用捕获。

关于C++ lambda表达式更加详细的说明可参考博客博客

要注意的是reserveresize的不同。

  • reserve只调整数组的capacity,并不分配元素分配内存,因此需要则copy_if的第三个参数使用back_insterter
  • resize为元素分配好了空间,如果在copy_if的第三个参数使用back_inserter,则会在已经分配内存的元素后面插入,正确地做法是使用vecb.begin(),对已经分配内存的元素进行覆盖。

remove_copy_if感觉和copy_if差不多,就是逻辑相反,使用remove_copy_if会在第三个参数的位置上收集到第四个参数返回false的元素。具体差别还要在继续查查资料。