c++ 转换和 lambda - 替换 for 循环

Posted

技术标签:

【中文标题】c++ 转换和 lambda - 替换 for 循环【英文标题】:c++ transform and lambda - replace for loop 【发布时间】:2017-07-12 08:02:00 【问题描述】:

我想用 std::transform 替换 for 循环。由于我对算法和 lambda 函数的经验很少,我想知道这是否是正确的方法

原始代码

for (size_t i=0; i < dataPhase.size(); ++i)

    dataPhase[i] = fmod(dataPhase[i], pi*1.00001);

std::transform 与 lambda

std::transform(dataPhase.begin(), dataPhase.end(), dataPhase.begin(), 
               [](double v)return fmod(v, pi*1.00001); 
);

我需要在这里抓拍吗?

在这种使用索引的情况下,我可以做些什么来替换 for 循环,如下代码所示:

const int halfsize = int(length/2);
for (size_t i=0; i < length; ++i)

    axis[i] = int(i) - halfsize;

编辑: 我想扩展问题(如果允许)。

在这种情况下是否可以用不同的东西替换 for 循环

for(std::vector<complex<double> >::size_type i = 0; i != data.size(); i++) 
    dataAmplitude[i] = abs(data[i]);
    dataPhase[i]     = arg(data[i]);

这里没有修改原始向量,而是将其值用于两个不同的向量。

【问题讨论】:

如果你得到的结果,它可能是正确的。你观察什么?算法适用于迭代器而不是索引。将第二个 sn-p 转换为使用算法是没有意义的,因为在这种情况下,带有索引的循环更具可读性 std::transform 也可用于没有明确索引的类型(如链表等)。只要前两个参数满足“输入迭代器”类别,第三个参数满足“输出迭代器”要求。因此,函数中没有(概念)索引。 @Matthias Pospiech 在尝试删除所有 for 循环之前,也许您可​​以看看我的评论 #1.... 大多数情况下,这会阻止您使用 OpenMP。 【参考方案1】:

第 1 部分)

这里不需要捕获,因为您只在 lambda 代码中使用参数 (v) 和全局变量 (pi)。

仅当 lambda 必须从当前范围访问变量(即在您的函数中声明)时才需要捕获。您可以按引用 (&) 或按值 (=) 捕获。

这是一个示例,其中需要“通过引用捕获”,因为“结果”是从 lambda 中修改的(但它也捕获了“搜索值”):

size_t count(const std::vector<char>& values, const char searchValue)

 size_t result = 0;
 std::for_each(values.begin(), values.end(), [&](const char& v) 
  if (v == searchValue)
   ++result;
 );
 return result;

(在现实世界中请使用std::count_if() 甚至std::count()

编译器为每个捕获的 lamda 创建一个未命名的仿函数(请参阅this question)。函数的构造函数接受参数并将其存储为成员变量。因此,“按值捕获”始终使用元素在定义 lambda 时的值。

下面是编译器可以为我们之前创建的 lambda 生成的代码示例:

class UnnamedLambda

public:
 UnnamedLambda(size_t& result_, const char& searchValue_)
  : result(result_), searchValue (searchValue_)
 

 void operator()(const char& v)
 
  // here is the code from the lambda expression
  if (v == searchValue)
   ++result;
 

private:
 size_t& result;
 const char& searchValue;
;

我们的函数可以重写为:

size_t count(const std::vector<char>& values, const char searchValue)

 size_t result = 0;
 UnnamedLambda unnamedLambda(result, searchValue);
 for(auto it = values.begin(); it != values.end(); ++it)
  unnamedLambda(*it);
 return result;

第 2 部分)

如果您需要索引,请继续使用 for 循环。

std::transform 允许处理单个元素,因此不提供索引。还有一些其他算法,例如 std::accumulate 可以处理中间结果,但我不知道任何提供索引的算法。

【讨论】:

【参考方案2】:

您的所有示例都可以转换为 std::transform 的使用,并使用一些额外的对象来做腿部工作(我在这里使用 boost,因为它是大多数所需类的现有技术)

// 1
for (size_t i=0; i < dataPhase.size(); ++i)

    dataPhase[i] = fmod(dataPhase[i], pi*1.00001);


// 2
const int halfsize = int(length/2);
for (size_t i=0; i < length; ++i)

    axis[i] = int(i) - halfsize;


// 3
for(std::vector<complex<double> >::size_type i = 0; i != data.size(); i++) 
    dataAmplitude[i] = abs(data[i]);
    dataPhase[i]     = arg(data[i]);

正如你正确注意到的,1 变成了

std::transform(dataPhase.begin(), dataPhase.end(), dataPhase.begin(), 
    [](double v)return fmod(v, pi*1.00001);  );

2需要一个数字序列,所以我用boost::integer_range

const int halfsize = int(length/2);
 // provides indexes 0, 1, ... , length - 1
boost::integer_range<int> interval = boost::irange(0, length);
std::transform(interval.begin(), interval.end(), axis.begin(), 
    [halfsize](int i) return i - halfsize;); 

3 涉及一对(2 元组)输出,所以我使用boost::zip_iterator 作为目标

std::transform(data.begin(), data.end(), 
    // turns a pair of iterators into an iterator of pairs
    boost::make_zip_iterator(dataAmplitude.begin(), dataPhase.begin()), 
    [](complex<double> d)  return boost::make_tuple(abs(d), arg(d)); );

【讨论】:

好的。我对替换 for 循环感兴趣有两个原因:非常高的数组的可读性和速度(以及未来对标准算法的多线程支持)。 1) 看起来是一个合理的替代品,但是 2 尤其是 3) 缺乏可读性并引入了不必要的复杂性。因此非常有趣,但我没有考虑实施。 对于2,你可以用boost::irange(-halfsize, halfsize)std::copy代替,但我用std::transform表达了这些 @MatthiasPospiech 我不确定你是如何决定“不必要的复杂性”的,除了“引入第 3 方库”。可读性是主观的,你可以习惯“功能”风格【参考方案3】:

这里有一些lambda captures的例子:

[] 不捕获任何内容

[&] 通过引用捕获任何被引用的变量

[=] 通过复制来捕获任何引用的变量

[=, &foo] 通过复制来捕获任何引用的变量,但是 通过引用捕获变量 foo

[bar] 通过复制捕获bar;不要复制任何东西

[this] 捕获封闭类的this指针

因此,如果您的示例中的 pilocal 变量(不是宏或全局变量),您不能让 [] 而是使用 [pi] 来捕获复制(这对于 double 来说是正常的):

std::transform(dataPhase.begin(), dataPhase.end(), dataPhase.begin(), 
           [pi](double v)return fmod(v, pi*1.00001); 
);

对于您的第二个示例,没有内置的 std::transform 提供索引。我认为更好的解决方案是保留你的 for 循环。

如果你真的想使用 lambda(作为练习),你可以使用:

const int halfsize = int(length/2);

auto lambda=[&axis,halfsize](const int i)axis[i] = i - halfsize;;

for (size_t i=0; i < length; ++i)

    lambda(i);

const int halfsize = int(length/2);

auto lambda=[halfsize](const int i)return i - halfsize;;  

for (size_t i=0; i < length; ++i)

   axis[i] = lambda(i);

这仅取决于您希望如何设计代码。

备注 1: 似乎您希望避免“基本”for 循环,但它们并不是必要的邪恶,特别是如果您想使用 OpenMP 获得一些性能(simd 或多线程) .比如

 #pragma omp simd
 for(auto& v_i :v)    // or worst std::transform
    v_i = fmod(v_i, pi*1.00001);
 

不支持且不会编译。

然而

 #pragma omp simd
 for (size_t i=0; i < dataPhase.size(); ++i)
 
     dataPhase[i] = fmod(dataPhase[i], pi*1.00001);
     

可以使用 g++ -fopenmp ... 如果可以使用 simd 则具有潜在的性能增益。关于多线程,可以说即将支持 STL 算法的并行执行,但这仅适用于 C++17。

备注 2: 不是在 C++ 中,而是在 D language 中,您有一个 foreach 指令,可以让您选择性地包含索引:

foreach (e; [4, 5, 6])  writeln(e); 
// 4 5 6

但是

foreach (i, e; [4, 5, 6])  writeln(i, ":", e); 
// 0:4 1:5 2:6

【讨论】:

我不明白为什么要捕获 pi。它是一个常数。我们使用过但没有退回。而且由于它是一个常数,甚至不能被修改。如果我想修改 pi,捕获它是有意义的。 如果你不使用 [pi] 你会得到一个编译器错误(对于 gcc,类似“错误:'pi' 没有被捕获)。即使没有修改这个 [pi] 定义了” context" 为你的 lambda。例如,如果你想将你的 lambda 复制到另一个地方,你还必须复制 pi。这个上下文是执行 closure 操作所必需的。 如果 pi 是全局常量(在函数 ar 类之外声明),则不必捕获它。 (捕获)lambda 就像一个方法调用。仅当此方法必须访问任何 local 变量时,您必须捕获它们。非捕获 lambda 的工作方式类似于全局函数,甚至可以用于需要全局函数的地方(在当前 C++ 中)。 @AndreasH。你是对的,我假设了一个 local 变量。谢谢你的澄清。

以上是关于c++ 转换和 lambda - 替换 for 循环的主要内容,如果未能解决你的问题,请参考以下文章

将多级 for 循环转换为 linq 或 lambda 表达式

如何使用 C++ lambda 将成员函数指针转换为普通函数指针以用作回调

c++中lambda表达式用法

C++进阶 :Lambda 表达式及底层实现原理详解

c++循环链表的递归基本情况

C++ lambda 语法