说我有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。
答案 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));