C++20 Ranges与函数式编程举例
·
我来详细介绍C++20 Ranges中的这些操作,并给出水仙花数的示例。
C++20 Ranges 核心操作
- transform - 对每个元素应用函数
#include <ranges>
#include <vector>
#include <iostream>
auto nums = std::vector{1, 2, 3, 4, 5};
auto doubled = nums | std::views::transform([](int x) { return x * 2; });
// 结果: 2, 4, 6, 8, 10
- filter - 过滤满足条件的元素
auto even = nums | std::views::filter([](int x) { return x % 2 == 0; });
// 结果: 2, 4
- take - 取前n个元素
auto first3 = nums | std::views::take(3);
// 结果: 1, 2, 3
- sort 和 unique - 排序和去重
注意:这些不是视图,而是操作
#include <algorithm>
std::ranges::sort(nums); // 原地排序
auto unique_view = nums | std::views::unique;
// 注意:unique_view只是视图,不会改变原容器
- sum 计算
C++20 Ranges中没有直接的sum,但可以配合std::accumulate:
#include <numeric>
int total = std::accumulate(nums.begin(), nums.end(), 0);
完整示例:水仙花数集合
#include <iostream>
#include <vector>
#include <ranges>
#include <cmath>
#include <algorithm>
// 判断是否为水仙花数(n位数,各位数字的n次方之和等于本身)
bool isNarcissistic(int num) {
int sum = 0;
int temp = num;
int n = static_cast<int>(std::to_string(num).length());
while (temp > 0) {
int digit = temp % 10;
sum += static_cast<int>(std::pow(digit, n));
temp /= 10;
}
return sum == num;
}
// 生成水仙花数集合
std::vector<int> getNarcissisticNumbers(int limit) {
using namespace std::ranges;
using namespace std::views;
// 生成1到limit的序列
auto numbers = iota(1, limit + 1);
// 过滤出水仙花数
auto narcissistic = numbers | filter(isNarcissistic);
// 转换为vector并返回
return std::vector<int>(narcissistic.begin(), narcissistic.end());
}
// 更优雅的版本:使用管道操作
auto getNarcissisticNumbersRange(int start, int end) {
return std::views::iota(start, end + 1)
| std::views::filter(isNarcissistic);
}
int main() {
std::cout << "水仙花数 (1-1000):\n";
// 方法1:获取vector
auto narcissistic1 = getNarcissisticNumbers(1000);
for (int num : narcissistic1) {
std::cout << num << " ";
}
std::cout << "\n\n";
// 方法2:直接使用range视图(惰性求值)
std::cout << "使用range视图:\n";
for (int num : getNarcissisticNumbersRange(1, 10000)) {
std::cout << num << " ";
}
std::cout << "\n\n";
// 方法3:结合更多操作
std::cout << "三位数的水仙花数:\n";
auto threeDigitNarcissistic = std::views::iota(100, 1000)
| std::views::filter(isNarcissistic);
for (int num : threeDigitNarcissistic) {
std::cout << num << " ";
}
std::cout << "\n";
return 0;
}
更复杂的组合示例
#include <iostream>
#include <vector>
#include <ranges>
#include <algorithm>
void complexExample() {
using namespace std::ranges;
using namespace std::views;
std::vector<int> numbers{153, 370, 371, 407, 123, 456, 789, 1634};
// 组合多个操作:过滤、排序、去重
auto processed = numbers
| filter([](int x) { return x >= 100 && x <= 999; }) // 只取三位数
| transform([](int x) { return x + 1; }) // 每个数加1
| common; // 转换为common_range以便排序
// 转换为vector进行排序(range视图本身是惰性的)
std::vector<int> result(processed.begin(), processed.end());
std::ranges::sort(result);
std::cout << "处理后的水仙花数+1并排序:\n";
for (int num : result) {
std::cout << num << " ";
}
std::cout << "\n";
}
int main() {
complexExample();
return 0;
}
关键点总结
- 惰性求值:Ranges视图是惰性的,只有在遍历时才计算
- 管道操作符 |:使代码更易读
- 视图 vs 操作:
· 视图(views):transform, filter, take等,返回视图对象
· 操作(algorithms):sort, unique等,可能修改容器 - 性能优势:避免中间容器的创建,提高性能
- 可组合性:可以轻松组合多个操作
水仙花数的常见值:153, 370, 371, 407, 1634, 8208, 9474, 54748, …
你说得对!我们可以用 transform 来计算水仙花数的各位数字幂次和。让我重写水仙花数判断函数,充分利用 Ranges 的特性:
#include <iostream>
#include <vector>
#include <ranges>
#include <cmath>
#include <numeric>
#include <string>
#include <algorithm>
// 使用Ranges的transform计算各位数字的幂次和
bool isNarcissistic(int num) {
using namespace std::ranges;
using namespace std::views;
std::string numStr = std::to_string(num);
int n = numStr.length();
// 将字符串转换为各位数字
auto digits = numStr | transform([](char c) { return c - '0'; });
// 计算每位数字的n次方之和
auto sum = std::accumulate(
digits.begin(),
digits.end(),
0,
[n](int total, int digit) {
return total + static_cast<int>(std::pow(digit, n));
}
);
return sum == num;
}
// 或者更函数式的写法
bool isNarcissisticFunctional(int num) {
using namespace std::ranges;
using namespace std::views;
std::string numStr = std::to_string(num);
int n = numStr.length();
// 一行代码完成:将数字转换为字符串,提取每位数字,计算幂次和
auto powerSum = std::accumulate(
numStr.begin(),
numStr.end(),
0,
[n](int total, char c) {
int digit = c - '0';
return total + static_cast<int>(std::pow(digit, n));
}
);
return powerSum == num;
}
// 另一种方法:使用整数运算配合ranges
bool isNarcissisticRangeOnly(int num) {
using namespace std::views;
int original = num;
int n = static_cast<int>(std::to_string(num).length());
// 创建一个从num提取各位数字的生成器
auto digits = [num]() mutable {
return transform_view{
iota(0) | take_while([num](int) { return num > 0; }),
[&num](int) {
int digit = num % 10;
num /= 10;
return digit;
}
};
}();
// 计算幂次和
int sum = 0;
for (int digit : digits) {
sum += static_cast<int>(std::pow(digit, n));
}
return sum == original;
}
// 使用管道操作生成水仙花数范围
auto generateNarcissisticNumbers(int start, int end) {
using namespace std::ranges;
using namespace std::views;
return iota(start, end + 1)
| filter(isNarcissistic)
| common; // 转换为common_range
}
// 更复杂的示例:使用transform处理结果
void narcissisticTransformExample() {
using namespace std::ranges;
using namespace std::views;
std::cout << "水仙花数及其平方:\n";
auto narcissisticNumbers = iota(100, 10000)
| filter(isNarcissistic)
| transform([](int n) {
return std::make_pair(n, n * n); // 返回原数和平方
});
for (auto [num, square] : narcissisticNumbers) {
std::cout << num << " -> " << square << "\n";
}
}
// 使用transform计算每个水仙花数的各位数字
void analyzeNarcissisticNumbers() {
using namespace std::ranges;
using namespace std::views;
std::cout << "\n水仙花数各位数字分析:\n";
auto analysis = iota(100, 10000)
| filter(isNarcissistic)
| transform([](int num) {
std::string str = std::to_string(num);
int n = str.length();
// 提取各位数字
auto digits = str
| transform([](char c) { return c - '0'; })
| std::ranges::to<std::vector<int>>();
// 计算幂次
auto powers = digits
| transform([n](int d) {
return static_cast<int>(std::pow(d, n));
})
| std::ranges::to<std::vector<int>>();
return std::make_tuple(num, digits, powers);
});
for (const auto& [num, digits, powers] : analysis) {
std::cout << num << " = ";
for (size_t i = 0; i < digits.size(); ++i) {
std::cout << digits[i] << "^" << digits.size();
if (i < digits.size() - 1) std::cout << " + ";
}
std::cout << " = ";
for (size_t i = 0; i < powers.size(); ++i) {
std::cout << powers[i];
if (i < powers.size() - 1) std::cout << " + ";
}
std::cout << "\n";
}
}
int main() {
std::cout << "三位数水仙花数:\n";
// 生成并输出水仙花数
auto narcissistic = generateNarcissisticNumbers(100, 1000);
for (int num : narcissistic) {
std::cout << num << " ";
}
std::cout << "\n\n";
// 使用transform处理结果
narcissisticTransformExample();
// 详细分析
analyzeNarcissisticNumbers();
// 测试各种方法的一致性
std::cout << "\n测试各种实现的一致性:\n";
for (int num : {153, 370, 371, 407, 1634, 8208, 9474}) {
bool r1 = isNarcissistic(num);
bool r2 = isNarcissisticFunctional(num);
bool r3 = isNarcissisticRangeOnly(num);
std::cout << num << ": "
<< r1 << " " << r2 << " " << r3
<< " (所有方法应该一致)\n";
}
return 0;
}
更简化的版本(C++23)
如果你的编译器支持C++23,可以更简洁:
#include <iostream>
#include <ranges>
#include <cmath>
#include <numeric>
// C++23简化版
auto isNarcissisticCpp23(int num) -> bool {
std::string s = std::to_string(num);
int n = s.length();
// 使用fold计算总和
return std::ranges::fold_left(
s,
0,
[n](int sum, char c) {
int digit = c - '0';
return sum + static_cast<int>(std::pow(digit, n));
}
) == num;
}
// 使用C++23的to<>将range转为容器
auto getNarcissisticNumbersCpp23(int max) {
using namespace std::ranges;
using namespace std::views;
return iota(1, max + 1)
| filter(isNarcissisticCpp23)
| to<std::vector<int>>();
}
int main() {
auto numbers = getNarcissisticNumbersCpp23(10000);
std::cout << "水仙花数 (1-10000):\n";
for (int num : numbers) {
std::cout << num << " ";
}
std::cout << "\n";
return 0;
}
关键改进点:
- 使用transform提取数字:将字符串转换为数字序列
- 结合accumulate计算和:使用标准算法计算幂次和
- 函数式风格:更符合Ranges的设计理念
- 可组合性:可以轻松与其他Ranges操作组合
性能考虑:
虽然使用transform和字符串转换的方法很优雅,但性能可能不如直接使用整数运算。在实际应用中,可以根据需求选择:
· 代码简洁性优先:使用字符串+transform方法
· 性能优先:使用传统的整数运算方法
· 学习/演示:展示Ranges的各种用法
这样改写后,水仙花数的判断逻辑完全融入了Ranges的函数式编程风格!
更多推荐
所有评论(0)