使用标准算法复制每个其他元素(下采样)

时间:2015-06-13 10:22:14

标签: c++ algorithm c++11 vector stl

说我有std::vector个N元素。我想将它的每个第n个元素复制到一个新的向量,或平均到那个元素然后复制它(下采样原始向量)。所以我想这样做

std::vector<double> vec(N);
long n = 4;
std::vector<double> ds(N/n);
for(long i = 0; i < ds.size(); i+=n)
{
    ds[i] = vec[i*n];
}

for(long i = 0; i < ds.size(); i+=n)
{
    double tmp = 0;    
    for(long j = 0; j < n; j++)
    {
        tmp += vec[i*n+j];
    }
    ds[i] = tmp/static_cast<double>(n);
}

有没有办法使用C ++的标准算法?喜欢使用带二进制函数的std :: copy吗?我想用这种方式处理数十亿个元素,我希望这个元素尽可能快。

PS:我不想使用外部库,如boost。

4 个答案:

答案 0 :(得分:4)

为了便于阅读,正如Vlad在评论中指出的那样,循环将是一个好主意。但如果你真的想做这样的事情,你可以尝试:

int cnt=0,n=3; 
vector<int> u(v.size()/3); 
copy_if (v.begin(), v.end(), u.begin(), 
          [&cnt,&n] (int i)->bool {return ++cnt %n ==0; } ); 

如果你想平均,它会变得更糟,因为你需要将transform()copy_if()相结合的类似技巧。

修改 如果你正在寻找性能,你最好坚持循环,正如 davidhigh 的注释中所强调的那样:它将避免为每个元素调用lambda函数的开销。

如果您正在寻找一种算法,因为您经常这样做,那么您最好编写自己的通用算法。

答案 1 :(得分:3)

如果仅使用标准库功能和算法,并且如果不允许使用循环,则代码可以采用以下方式。考虑到代码基于C ++ 2014.如果您需要的代码将由仅支持C ++ 2011的编译器编译,那么您必须进行一些小的更改。

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

int main()
{
    const size_t N = 4;
    std::vector<double> src = { 1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9 };
    size_t n = src.size() / N;
    std::vector<double> dst( n );

    std::copy_if( src.begin(), std::next( src.begin(), n * N ), dst.begin(),
                  [i = 0] ( auto ) mutable { return ( i = ( i + 1 ) % N ) == 0; } );

    for ( double x : dst ) std::cout << x << ' ';
    std::cout << std::endl;

    dst.assign( n, 0.0 );

    std::accumulate( src.begin(), std::next( src.begin(), n * N ), dst.begin(),
                     [i = 0] ( auto acc, auto x ) mutable
                     {
                         *acc += x; 
                         if ( ( i = ( i + 1 ) % N ) == 0 )  *acc++ /= N;
                         return acc;
                     } );

    for ( double x : dst ) std::cout << x << ' ';
    std::cout << std::endl;
}    

程序输出

4.4 8.8 
2.75 7.15 

if条件中的此复合表达式

if ( ( i = ( i + 1 ) % N ) == 0 )  *acc++ /= N;

你可以用更简单的替换

if ( ++i % N == 0 )  *acc++ /= N;

答案 2 :(得分:2)

您可以根据<algorithm>中使用的设计原则编写自己的通用算法。

对于每n个元素的副本:

template<class in_it, class out_it>
out_it copy_every_n( in_it b, in_it e, out_it r, size_t n) {
    for (size_t i=distance(b,e)/n; i--; advance (b,n)) 
        *r++ = *b;
    return r;
}

使用示例:

vector<int> v {1,2,3,4,5,6,7,8,9,10};
vector<int> z(v.size()/3); 
copy_every_n(v.begin(), v.end(), z.begin(), 3);     

要将元素n乘以n,您可以使用:

template<class in_it, class out_it>
out_it average_every_n( in_it b, in_it e, out_it r, size_t n) {
    typename out_it::value_type tmp=0;
    for (size_t cnt=0; b!=e; b++)  {
        tmp+=*b;
        if (++cnt==n) {
            cnt=0; 
            *r++=tmp/n;
            tmp=0;
        }
    }
    return r;
}

使用示例:

vector<int> w(v.size()/3); 
average_every_n(v.begin(), v.end(), w.begin(), 3);  

优于你的初始循环,这不仅适用于向量,而且适用于提供begin()end()迭代器的任何容器。它避免了我在其他答案中指出的开销。

答案 3 :(得分:1)

你可能已经明确表示你不想使用Boost,但任何非Boost解决方案都会真正实现这种事情,所以我将告诉你如何在Boost中做到这一点。最终,我认为你最好写一个简单的循环。

下采样使用strided

boost::copy(
        input | strided(2),
        std::back_inserter(output));

下采样平均值另外使用transformed,尽管此解决方案是非通用的,并且特别依赖于vector连续:

boost::copy(
        input | strided(2) | transformed([](auto& x){
                return std::accumulate(&x, &x + 2, 0) / 2.;
            }),
        std::back_inserter(output));

当然,如果输入不是步幅长度的精确倍数,则会出现问题,因此最好做以下事情:

auto downsample_avg = [](auto& input, int n){
    return input | strided(n) | transformed([&,n](auto& x){
        auto begin = &x;
        auto end = begin + std::min<size_t>(n, &input.back() - begin + 1);
        return std::accumulate(begin, end, 0.0) / (end - begin);
    });
};

boost::copy(
    downsample_avg(input, 2),
    std::back_inserter(output));