看到一个介绍 C++17 的系列博文(原文),有十来篇的样子,觉得挺好,看看有时间能不能都简单翻译一下,这是第七篇~

C++17 对 STL 算法的改动,概念上其实很简单.标准库之前有超过100个算法,内容包括搜索,计数,区间及元素操作等等.新标准重载了其中69个算法并新增了7个算法.重载的算法和新增的算法都支持指定一个所谓执行策略(execution policy)的参数,通过调整这个参数,你可以指定算法是以串行,并行或者矢量并行的方式来运行.

我之前的文章介绍了很多重载的标准库算法,有兴趣的朋友可以看看.

这次,我要介绍一下 C++17 新引入的7个算法:

std::for_each_nstd::exclusive_scan
std::inclusive_scanstd::transform_exclusive_scan
std::transform_inclusive_scanstd::parallel::reduce
std::parallel::transform_reduce

其中除了 std::for_each_n 之外,其他几个算法的名字都很特殊.为了理解方便,我先介绍一下 Haskell 中相关的内容,之后再回到C++的讲解中.

A short detour

C++17 新引入的算法在纯函数式语言 Haskell 中都有对应的方法.

  • for_each_n 对应的方法为 map.
  • exclusive_scan 和 inclusive_scan 对应的方法为 scanl 和 scanl1
  • transform_exclusive_scan 等同于组合使用 map 和 scanl, 而 transform_inclusive_scan 等同于组合或者 map 和 scanl1.
  • reduce 对应 foldl 或者 foldl1.
  • transform_reduce 对应 map 和 foldl 的组合或者 map 和 foldl1 的组合.

开始讲解之前,让我简单说一下这些方法的功能作用.

  • map 可以对一个列表应用一个函数
  • foldl 和 foldl1 可以对一个列表应用一个二元运算并将结果归纳为一个数值.foldl 与 foldl1 相比额外需要一个初始值.
  • scanl 和 scanl1 的操作与 foldl 和 foldl1 基本一致,但是他们会产生所有的中间结果,所以最终你会获得一个列表,而不是一个数值.
  • foldl, foldl1, scanl 和 scanl1 的操作都是从列表的左侧开始.

下面是一个 Haskell 的相关示例

(1) 和 (2) 处的代码分别定义了一个整数列表(ints)和一个字符串列表(strings).在 (3) 中,我给整数列表(ints)应用了一个 lambda 函数(\a -> a * a).(4) 和 (5) 则更加复杂些:(4) 中我将整数列表中的所有整数对相乘(乘法单位元素1作为初始元素).(5) 中则做了所有整数对相加的操作.(6), (7), 和 (9) 中的操作可能有些难以理解,你必须从右往左来阅读这几个表达式.scanl1 (+) . map(\a -> length a) (即(7)) 是一个函数组合,其中的点号(.)用以组合左右两个函数.第一个函数将列表中的元素映射为元素的长度,第二个函数则将这些映射的长度相加.(9) 中的操作和 (7) 很相似,不同之处在于 foldl 只产生一个数值(而不是列表)并且需要一个初始元素(我指定初始元素为0),现在,表达式(8)看起来应该比较简单了,他以":"作为分隔符连接了列表中的各个字符串元素.

我想你也许好奇为什么我要在介绍C++的文章中写这么多 Haskell 的内容(这些内容还颇具挑战性),那是因为两个原因:

  1. 你可以知道 C++ 中相应算法的历史
  2. 比照 Haskell 的对应方法可以帮助我们理解 C++ 中 的相应算法.

好了,我们终于看个C++的示例了.

The seven new algorithms

做好心理准备,下面的代码可能有些难读.

#include <iostream>
#include <string>
#include <vector>
#include <execution>int main()
{std::cout << std::endl;// for_each_nstd::vector<int> intVec{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };                        // 1std::for_each_n(std::execution::par,                       // 2intVec.begin(), 5, [](int& arg) { arg *= arg; });std::cout << "for_each_n: ";for (auto v : intVec) std::cout << v << " ";std::cout << "\n\n";// exclusive_scan and inclusive_scanstd::vector<int> resVec{ 1, 2, 3, 4, 5, 6, 7, 8, 9 };std::exclusive_scan(std::execution::par,                   // 3resVec.begin(), resVec.end(), resVec.begin(), 1,[](int fir, int sec) { return fir * sec; });std::cout << "exclusive_scan: ";for (auto v : resVec) std::cout << v << " ";std::cout << std::endl;std::vector<int> resVec2{ 1, 2, 3, 4, 5, 6, 7, 8, 9 };std::inclusive_scan(std::execution::par,                   // 5 resVec2.begin(), resVec2.end(), resVec2.begin(),[](int fir, int sec) { return fir * sec; }, 1);std::cout << "inclusive_scan: ";for (auto v : resVec2) std::cout << v << " ";std::cout << "\n\n";// transform_exclusive_scan and transform_inclusive_scanstd::vector<int> resVec3{ 1, 2, 3, 4, 5, 6, 7, 8, 9 };std::vector<int> resVec4(resVec3.size());std::transform_exclusive_scan(std::execution::par,         // 6resVec3.begin(), resVec3.end(),resVec4.begin(), 0,[](int fir, int sec) { return fir + sec; },[](int arg) { return arg *= arg; });std::cout << "transform_exclusive_scan: ";for (auto v : resVec4) std::cout << v << " ";std::cout << std::endl;std::vector<std::string> strVec{ "Only", "for", "testing", "purpose" };             // 7std::vector<int> resVec5(strVec.size());std::transform_inclusive_scan(std::execution::par,         // 8strVec.begin(), strVec.end(),resVec5.begin(),[](auto fir, auto sec) { return fir + sec; },[](auto s) { return s.length(); });std::cout << "transform_inclusive_scan: ";for (auto v : resVec5) std::cout << v << " ";std::cout << "\n\n";// reduce and transform_reducestd::vector<std::string> strVec2{ "Only", "for", "testing", "purpose" };std::string res = std::reduce(std::execution::par,         // 9strVec2.begin() + 1, strVec2.end(), strVec2[0],[](auto fir, auto sec) { return fir + ":" + sec; });std::cout << "reduce: " << res << std::endl;// 11std::size_t res7 = std::transform_reduce(std::execution::par,strVec2.begin(), strVec2.end(), 0u,[](std::size_t a, std::size_t b) { return a + b; },[](std::string s) { return s.length(); });std::cout << "transform_reduce: " << res7 << std::endl;std::cout << std::endl;return 0;
}

与 Haskell 中的示例对应,我使用 std::vector 创建了整数列表 (1) 和字符串列表 (7).

在代码 (2) 处,我使用 for_each_n 将(整数)列表的前5个整数映射成了整数自身的平方.

exclusive_scan (3) 和 inclusive_scan (5) 非常相似,都是对操作的元素应用一个二元运算,区别在于 exclusive_scan 的迭代操作并不包含列表的最后一个元素, Haskell 中对应的表达式为: scanl (*) 1 ints.(译注:结果并不完全等同, Haskell 的 scanl 操作包含列表最后一个元素,后面提到的相关 Haskell 对应也是如此,注意区别)

transform_exclusive_scan (6) 执行的操作有些复杂,他首先将 lambda 函数 function [](int arg){ return arg *= arg; } 应用到列表 resVec3 的每一个元素上,接着再对中间结果(由上一步 lambda 函数产生的临时列表)应用二元运算 [](int fir, int sec){ return fir + sec; }(以 0 作为初始元素),最终结果存储于 resVec4.begin() 开始的内存处.Haskell 中对应表达式为:
scanl (+) 0 . map(\a -> a * a) $ ints.

(8) 中的 transform_inclusive_scan 和 transform_exclusive_scan 所执行的操作很类似,其中第一步的 lambda 函数将元素映射为了元素的长度,对应的 Haskell 表达式为:
scanl1 (+) . map(\a -> length a) $ strings.

现在,代码中的 reduce 函数 (9) 看起来就比较简单了,他需要在各个(字符串)元素之间放置 “:” 字符.因为结果的开头不能带有 “:” 字符, reduce 的迭代是从第二个元素开始的(strVec2.begin() + 1) ,并以第一个元素作为初始元素(strVec2[0]). Haskell 中对应表达式为: foldl1 (\l r -> l ++ “:” ++ r) strings.

如果你想深入了解一下 (11) 中的 transform_reduce,可以看看我之前的文章,这里同样给出 Haskell 中对应的表达式: foldl (+) 0 . map (\a -> length a) $ strings.

程序的输出如下,有兴趣的朋友可以仔细看看.

Final remarks

C++17 新引入的这7个算法有很多重载版本,调用的时候,你可以指定初始元素,也可以不指定初始元素,同样的,你可以指定执行策略,也可以不指定执行策略.你甚至可以在不指定二元运算的情况下调用需要二元运算的算法(例如std::reduce),这种情况下,这些算法会默认使用二元加法运算.为了能够以并行或者矢量并行的方式运行这些算法,指定给算法的二元运算必须满足可结合性,这个限制也很容易理解,因为并行化的算法很容易会在多个CPU核上同时运行(这种情况下,二元运算不可结合的话就会导致错误结果).更深入的一些信息你可以看看这里和这里.

[译]C++17,标准库新引入的并行算法相关推荐

  1. [译]C++17,标准库有哪些新变化?

    看到一个介绍 C++17 的系列博文(原文),有十来篇的样子,觉得挺好,看看有时间能不能都简单翻译一下,这是第二篇~ C++17 有许多新的标准库变化,简单起见,这篇文章只介绍了以下内容:std::s ...

  2. STM32标准库的引入视频课程-第3季第6部分-朱有鹏-专题视频课程

    STM32标准库的引入视频课程-第3季第6部分-1017人已学习 课程介绍         本课程是<朱有鹏老师单片机完全学习系列课程>第3季第6个课程,本课程详细讲解了STM32官方新标 ...

  3. 视频教程-STM32标准库的引入视频课程-第3季第6部分-单片机/工控

    STM32标准库的引入视频课程-第3季第6部分 互联网课程品牌<朱老师物联网大讲堂>创始人.精通U-Boot.Linux kernel移植及驱动程序开发;精通C.C++.Java.C#等语 ...

  4. C++ Primer 5th笔记(chap 17 标准库特殊设施)IO库 之操纵符

    1. 控制浮点数格式 可以控制浮点数输出三个种格式 • 以多高精度( 多少个数字) 打印浮点值 • 数值是打印为十六进制. 定点十进制还是科学记数法形式 • 对于没有小数部分的浮点值是否打印小数点 1 ...

  5. C++ Primer 5th笔记(chap 17 标准库特殊设施)匹配标志

    1. 用来控制匹配和格式的标志 标准库还定义了用来在替换过程中控制匹配或格式的标志,这些标志可以传递给函数 regex_search 或 regex_match 或是类 smatch 的 format ...

  6. C++ Primer 5th笔记(chap 17 标准库特殊设施)正则表达式错误

    1. 指定或使用正则表达式时的错误 如果我们编写的正则表达式存在错误, 则在运行时标准库会抛出一个类型为regex 的异常 eg. try {// alnum右括号少了一个, 构造函数会抛出异常reg ...

  7. C++ Primer 17 标准库特殊设置

    标准库特殊设置 1 tuple 类型(元组) tuple 是类似 pair 的模板.每个 pair 的成员类型都不相同,但每个 pair 都恰好有两个成员.不同 tuple 类型的成员类型也不相同,但 ...

  8. C++ Primer 5th笔记(chap 17 标准库特殊设施)流随机访问

    1. 流类型通常都支持对流中数据的随机访问. 可以重定位流, 使之跳过一些数据, 首先读取最后一行, 然后读取第一行, 依此类推. 1.1 标准厍提供了一对函数, 来定位( seek )到流中给定的位 ...

  9. C++ Primer 5th笔记(chap 17 标准库特殊设施)多字节低层IO操作

    1. 多字节操作 一些未格式化 IO 操作一次处理大块数据,这些操作要求我们自己分配并管理用来保存和提取数据的字符数组 操作 解释 is.get(sink, size, delim) 从is中读取最多 ...

最新文章

  1. 8.STM32中对ADC1_Config()函数(ADC1_GPIO_Config()和ADC1_Mode_Config())的理解(自定义)测试ADC转换电压值输出到终端上。
  2. 怎么将织梦图集模型编辑器改为文章编辑器?
  3. 条件概率 and 条件概率的链式法则 and 期望、方差和协方差
  4. leetcode 553. Optimal Division | 553. 最优除法(暴力递归->傻缓存)
  5. jQuery实现禁用和只读
  6. python中的and_我想问下python中 逻辑与:“”和“and”的区别
  7. 线上问题排查命令----Shell篇
  8. mysql 按照汉字的拼音排序、按照首字母分类
  9. 【数学建模】基于matlab模糊二元决策树【含Matlab源码 038期】
  10. 计算机模拟数学建模,计算机模拟数学建模
  11. 机器学习之Python分析圆周率
  12. cp105b linux 驱动,cp105b驱动下载-富士施乐cp105b驱动下载v2.6.15.0 官方最新版-西西软件下载...
  13. 应用comsol模拟水力压裂应力分布
  14. R语言计算dataframe中指定数据列的值为缺失值的样本个数(行的个数)
  15. build.sh脚本
  16. 【GPT-4】立即停止训练比 GPT-4 更强的模型,至少六个月!马斯克、图灵奖得主等数千 AI 专家紧急呼吁
  17. 下注玩客币到重仓公链,迅雷的区块链转型之路
  18. 计算机基础教研活动记录,信息技术教研活动记录
  19. uniapp 中如何使用echart_uni-app中使用Echarts绘画图表
  20. AVD Manager创建模拟器

热门文章

  1. 信息解码(ACM)刘汝佳
  2. 18_位运算符(了解)
  3. Win10电脑用户密码破解
  4. linux进去vi编辑器执行c语言代码,VI编辑器的使用与Linux操作系统下C语言的使用...
  5. 网络安全卷么? 你身边的网络安全人过的怎么样呢?
  6. DaoCloud docker加速器地址获取以及mac系统如何配置镜像加速
  7. 就决定是你啦!苏菲婆5! —— 谈谈我对Surface Pro 5的使用体验以及各种骚操作
  8. PCL打印语言初步了解
  9. <Linux开发>linux开发工具- 之-TFTP
  10. Java方法重写与重载