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指针
因此,如果您的示例中的 pi 是 local 变量(不是宏或全局变量),您不能让 [] 而是使用 [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 表达式