我来详细介绍C++20 Ranges中的这些操作,并给出水仙花数的示例。

C++20 Ranges 核心操作

  1. 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
  1. filter - 过滤满足条件的元素
auto even = nums | std::views::filter([](int x) { return x % 2 == 0; });
// 结果: 2, 4
  1. take - 取前n个元素
auto first3 = nums | std::views::take(3);
// 结果: 1, 2, 3
  1. sort 和 unique - 排序和去重

注意:这些不是视图,而是操作

#include <algorithm>
std::ranges::sort(nums);  // 原地排序
auto unique_view = nums | std::views::unique;
// 注意:unique_view只是视图,不会改变原容器
  1. 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;
}

关键点总结

  1. 惰性求值:Ranges视图是惰性的,只有在遍历时才计算
  2. 管道操作符 |:使代码更易读
  3. 视图 vs 操作:
    · 视图(views):transform, filter, take等,返回视图对象
    · 操作(algorithms):sort, unique等,可能修改容器
  4. 性能优势:避免中间容器的创建,提高性能
  5. 可组合性:可以轻松组合多个操作

水仙花数的常见值: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;
}

关键改进点:

  1. 使用transform提取数字:将字符串转换为数字序列
  2. 结合accumulate计算和:使用标准算法计算幂次和
  3. 函数式风格:更符合Ranges的设计理念
  4. 可组合性:可以轻松与其他Ranges操作组合

性能考虑:

虽然使用transform和字符串转换的方法很优雅,但性能可能不如直接使用整数运算。在实际应用中,可以根据需求选择:

· 代码简洁性优先:使用字符串+transform方法
· 性能优先:使用传统的整数运算方法
· 学习/演示:展示Ranges的各种用法

这样改写后,水仙花数的判断逻辑完全融入了Ranges的函数式编程风格!

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐