C++初学者指南-5.标准库(第二部分)–数值运算算法
文章目录
- C++初学者指南-5.标准库(第二部分)--数值运算算法
- iota (注意不是itoa函数)
- Reductions
- reduce
- transform_reduce
- 遗留操作(无法并行执行)
- accumulate (≈ reduce) C++98
- inner_product (≈ transform_reduce) C++98
- Scans (Prefix 'Sums')
- adjacent_difference
- inclusive_scan
- exclusive_scan
- transform_inclusive_scan
- transform_exclusive_scan
- 遗留操作(无法并行执行)
- partial_sum (≈ inclusive_scan) C++98
- 相关内容
不熟悉 C++ 的标准库算法? ⇒ 简介
#include <numeric>
Reductions 根据输入元素的序列产生一个结果。
Scans 生成一个结果序列,其元素数量与输入序列相同。
iota (注意不是itoa函数)
cppreference
std::vector<int> v;
v.resize(9,0);
// fill subrange (as shown in image)
iota(begin(v)+2, begin(v)+7, 1);
for (int x : v) { cout << x << ' '; } // 0 0 1 2 3 4 5 0 0
// fill entire vector
iota(begin(v), end(v), 3);
for (int x : v) { cout << x << ' '; } // 3 4 5 6 7 8 9 10 11
运行示例代码
cppreference
std::vector<int> v;
v.resize(5,0);
// NOTE: might not be available yet
// in many standard library implementations!
auto const result = ranges::iota(v, 3);
std::cout << result.value; // 8
for (int x : v) { cout << x << ' '; } // 3 4 5 6 7
运行示例代码
Reductions
reduce
归约运算⊕必须具有结合性和可交换性,因为它的应用顺序是不确定的。
cppreference
std::vector<int> v {1,9,7,3,2,8};
auto const sum = reduce(begin(v), end(v)); // 1+9+7+3+2+8 = 30
auto const s47 = reduce(begin(v), end(v), 47); // 47+1+9+7+3+2+8 = 77
std::vector<double> w {2.0, 1.5, 3.0, 1.5};
auto const product = reduce(begin(w), end(w), 1.0, std::multiplies<>{});
// double product = 1.0 * 2.0 * 1.5 * 3.0 * 1.5 = 13.5
// 并行执行: #include <execution>
auto const psum = reduce(std::execution::par, begin(v), end(v));
并行执行参考
运行示例代码
确保初始值ω的类型要么和输入元素的类型相同,要么是一个能表示更多值的类型!
// 较窄的初始值类型可能会导致信息丢失:
std::vector<double> v {1.2, 2.4};
auto const wtf = reduce(begin(v), end(v), 1);
cout << wtf; // 4 int ^
auto const sum = reduce(begin(v), end(v), 1.0);
cout << sum; // 4.6 double ^^^
运行示例算法
transform_reduce
归约操作⊕必须具有结合性和可交换性,投影函数 f 必须没有副作用、没有状态,因为它们的应用顺序没有保证。
cppreference
std::vector<int> v {3,2,4};
auto f = [](int x) { return x*x; };
auto const rf = transform_reduce(begin(v), end(v), 1, std::plus<>{}, f);
// rf = 1 + f(3) + f(2) + f(4) = 30
运行示例代码
这两种运算 ⊕ 和 ⊗ 都必须是具有结合性和交换性,因为它们的应用顺序没有保证。
cppreference
std::vector<double> x {1.0, 3.0, 5.0};
std::vector<double> y {2.0, 4.0, 8.0};
auto const rx1 = transform_reduce(begin(x), end(x), begin(y), 10.0);
// rx1 = 10 + (1⋅2)+(3⋅4)+(5⋅8) = 64
auto const rx2 = transform_reduce(
begin(x), end(x), begin(y), 0.0,
std::plus<>{}, std::divides<>{});
// rx2 = 0.0 + (1/2)+(3/4)+(5/8) = 1.875
运行示例程序
遗留操作(无法并行执行)
accumulate (≈ reduce) C++98
更喜欢 C++17 的 std::reduce,因为它也可以并行执行。只有在使用非交换或非结合的归约操作 ⊕ 时,才使用 accumulate。
cppreference
std::vector<int> v {1,9,7,3,2,8};
auto const sum = accumulate(begin(v), end(v), 0); // 1+9+7+3+2+8 = 30
auto const s47 = accumulate(begin(v), end(v), 47); // 47+1+9+7+3+2+8 = 77
std::vector<double> w {2.0, 1.5, 3.0, 1.5};
auto const product = accumulate(begin(w), end(w), 1.0, std::multiplies<>{});
// double product = 1.0 * 2.0 * 1.5 * 3.0 * 1.5 = 13.5
运行示例代码
确保初始值ω的类型与输入元素的类型相同,或者是可以表示更多值的类型!
// 较窄的初始值类型可能会导致信息丢失:
std::vector<double> v {1.2, 2.4};
auto const wtf = accumulate(begin(v), end(v), 0);
cout << wtf; // 3 int ^
auto const sum = accumulate(begin(v), end(v), 0.0);
cout << sum; // 3.6 double ^^^
运行示例代码
inner_product (≈ transform_reduce) C++98
更喜欢使用 C++17 的 std::transform_reduce,因为它也可以并行执行。
cppreference
std::vector<int> v {4,3,2,1};
std::vector<int> w {10,20,30,40};
auto const ip = inner_product(begin(v), end(v), begin(w), 50);
// ip = 50 + (4⋅10)+(3⋅20)+(2⋅30)+(1⋅40) = 250
std::vector<double> num {1.0, 3.0, 5.0};
std::vector<double> den {2.0, 4.0, 8.0};
auto const res = inner_product(
begin(num), end(num), begin(den), 0.0,
std::plus<>{}, std::divides<>{} );
// res = 0.0 + (1/2)+(3/4)+(5/8) = 1.875
运行示例代码
Scans (Prefix ‘Sums’)
目标范围必须能够接收所有生成的元素!
这意味着,目标容器必须适当调整大小。
标准算法无法(在大多数情况下也不可能)检查目标范围是否足够大。
试图复制超出目标容量的元素会导致未定义的行为!
adjacent_difference
操作 ⊖ 不应有副作用 / 不应是有状态的,因为应用的顺序是不确定的。
cpprefernece
std::vector<int> in {1,2,6,8,3,6};
// 确认输出能存放计算结果
std::vector<int> out;
out.resize(in.size());
adjacent_difference(begin(in), end(in), begin(out));
for (int x : out) { cout << x << ' '; } // 1 1 4 2 -5 3
// C++17: 能提供定制操作
adjacent_difference(begin(in), end(in), begin(out), std::plus<>{});
for (int x : out) { cout << x << ' '; } // 1 3 8 14 11 9
adjacent_difference(begin(in), end(in), begin(out), std::multiplies<>{});
for (int x : out) { cout << x << ' '; } // 1 2 12 48 24 18
运行示例代码
inclusive_scan
二元运算⊕必须具有结合性,因为它的应用顺序是不确定的。
cppreference
std::vector<int> in {2,1,7,5,3};
// make sure output can fit results
std::vector<int> out;
out.resize(in.size());
inclusive_scan(begin(in), end(in), begin(out));
for (int x : out) { cout << x << ' '; } // 2 3 10 15 18
inclusive_scan(begin(in), end(in), begin(out), std::minus<>{});
for (int x : out) { cout << x << ' '; } // 2 1 -6 -11 -14
// with offset '3':
inclusive_scan(begin(in), end(in), begin(out), std::plus<>{}, 3);
for (int x : out) { cout << x << ' '; } // 5 6 13 18 21
运行示例代码
exclusive_scan
二元运算⊕必须具有结合性,因为它的应用顺序是不确定的。
cppreference
std::vector<int> in {2,1,7,5,3};
// make sure output can fit results
std::vector<int> out;
out.resize(in.size());
exclusive_scan(begin(in), end(in), begin(out), 0);
for (int x : out) { cout << x << ' '; } // 0 2 3 10 15
// with offset '3':
exclusive_scan(begin(in), end(in), begin(out), 3);
for (int x : out) { cout << x << ' '; } // 3 5 6 13 18
exclusive_scan(begin(in), end(in), begin(out), 0, std::minus<>{});
for (int x : out) { cout << x << ' '; } // 0 -2 -3 -10 -15
运行示例代码
transform_inclusive_scan
运算⊕必须具有结合性,投影函数 f 必须没有副作用、没有状态,因为它们应用的顺序无法保证。
cppreference
// returns value only if even and 0 if odd
auto even_only = [](int x) { return (x & 1) ? 0 : x; };
std::vector<int> in {2,1,6,4,3};
// make sure output can fit results
std::vector<int> out;
out.resize(in.size());
transform_inclusive_scan(
begin(in), end(in), begin(out), std::plus<>{}, even_only);
for (int x : out) { cout << x << ' '; } // 2 2 8 12 12
// with offset '3':
transform_inclusive_scan(
begin(in), end(in), begin(out), std::plus<>{}, even_only, 3);
for (int x : out) { cout << x << ' '; } // 5 5 11 15 15
// with projection f(x) = -x2:
transform_inclusive_scan(
begin(in), end(in), begin(out), std::plus<>{},
[](int x) { return -(x*x); });
for (int x : out) { cout << x << ' '; } // -4 -5 -41 -57 -66
运行示例代码
transform_exclusive_scan
运算⊕必须具有结合性,投影函数 f 必须没有副作用、没有状态,因为它们应用的顺序无法保证。
cppreference
// returns value only if even and 0 if odd
auto even_only = [](int x) { return (x & 1) ? 0 : x; };
std::vector<int> in {2,1,6,4,3};
// make sure output can fit results
std::vector<int> out;
out.resize(in.size());
transform_exclusive_scan(
begin(in), end(in), begin(out), 0, std::plus<>{}, even_only);
for (int x : out) { cout << x << ' '; } // 0 2 2 8 12
// with offset '3':
transform_exclusive_scan(
begin(in), end(in), begin(out), 3, std::plus<>{}, even_only);
for (int x : out) { cout << x << ' '; } // 3 5 5 11 15
// with projection f(x) = -x2:
transform_exclusive_scan(
begin(in), end(in), begin(out), 0, std::plus<>{},
[](int x) { return -(x*x); });
for (int x : out) { cout << x << ' '; } // 0 -4 -5 -41 -57
运行示例代码
遗留操作(无法并行执行)
partial_sum (≈ inclusive_scan) C++98
更喜欢C++17的std::inclusive_scan,因为它也可以并行执行。只有在归约操作⊕是非结合的情况下才使用partial_sum。
cppreference
std::vector<int> in {1,1,2,2,1,1};
// make sure output can fit results
std::vector<int> out;
out.resize(in.size());
partial_sum(begin(in), end(in), begin(out));
for (int x : out) { cout << x << ' '; } // 1 2 4 6 7 8
partial_sum(begin(in), end(in), begin(out), std::minus<>{});
for (int x : out) { cout << x << ' '; } // 1 0 -2 -4 -5 -6
相关内容
视频:partial_sum, iota by Conor Hoekstra
视频:accumulate, reduce by Conor Hoekstra
标准算法概述
C++标准库算法介绍
标准序列容器(vector、deque、list、…)
标准关联容器(map、set、…)
标准序列视图
cppreference:算法库
cppreference:容器库
视频:什么是 C++ 标准库?
视频:一小时内掌握 105 个 STL 算法 (Jonathan Boccara,2018)
C++ 之旅:容器和算法
算法概述表:
附上原文链接
如果文章对您有用,请随手点个赞,谢谢!^_^