← 中文资源

简体中文版本

Rust 迭代器管道生成订单报表

用迭代器过滤退款订单、折叠统计、按客户分组并排序,全程只依赖标准库。

阅读英文原文

功能说明

示例以订单切片为输入,先生成已支付订单的引用列表,再用 sum 求净额、用 fold 同时求笔数与总额并计算平均值。随后通过 BTreeMap 的 entry API 按客户累计金额,排序后取前两名,最后收集退款订单编号。所有步骤都是迭代器链,不引入第三方 crate,适合在报表或对账任务中组合使用。

关键代码

filter 与 map 是惰性的,只有 sum、fold、collect 等消费操作才真正遍历。by_customer 以 &str 为键借用 orders 中的客户名,避免复制字符串。排序先用金额降序,再用客户名升序打破平局,保证输出稳定可测。fold 的累加器是元组,避免两次遍历。refunded_ids 单独过滤,体现同一数据源可按不同视角复用。

使用注意

示例假定至少有一笔已支付订单,否则 count 为 0,average_cents 会得到 NaN;生产代码应先判断空集合。金额使用 u64 分,不能表示负数或退款,退款通过 refunded 标记排除。BTreeMap 让输出按键有序,但按金额排名仍需显式排序。collect 会分配新 Vec,超大集合应考虑流式写出或分页。借用键要求 orders 在报表使用期间保持存活。

验证方式

运行 cargo run,预期输出 paid orders: 4、net: 15500 cents、average: 3875.00 cents,前两名客户依次为 sam 与 ada,退款订单编号为 [3]。可新增一笔退款订单确认它不进入净额和排名,或删除全部已支付订单,观察平均值变为 NaN 并据此补充空集合分支。

代码示例

use std::collections::BTreeMap;

#[derive(Debug, Clone)]
struct Order {
    id: u32,
    customer: String,
    cents: u64,
    refunded: bool,
}

fn main() {
    let orders = vec![
        Order { id: 1, customer: "ada".to_string(), cents: 4_500, refunded: false },
        Order { id: 2, customer: "lin".to_string(), cents: 2_300, refunded: false },
        Order { id: 3, customer: "ada".to_string(), cents: 9_900, refunded: true },
        Order { id: 4, customer: "ada".to_string(), cents: 1_200, refunded: false },
        Order { id: 5, customer: "sam".to_string(), cents: 7_500, refunded: false },
    ];

    let paid: Vec<&Order> = orders.iter().filter(|order| !order.refunded).collect();
    let net_cents: u64 = paid.iter().map(|order| order.cents).sum();

    let (count, total) = paid
        .iter()
        .fold((0usize, 0u64), |(count, total), order| {
            (count + 1, total + order.cents)
        });
    let average_cents = total as f64 / count as f64;

    let mut by_customer: BTreeMap<&str, u64> = BTreeMap::new();
    for order in &paid {
        *by_customer.entry(order.customer.as_str()).or_insert(0) += order.cents;
    }

    let mut ranked: Vec<(&str, u64)> = by_customer.into_iter().collect();
    ranked.sort_by(|a, b| b.1.cmp(&a.1).then_with(|| a.0.cmp(b.0)));

    let top: Vec<String> = ranked
        .iter()
        .take(2)
        .enumerate()
        .map(|(index, (customer, cents))| format!("{}. {customer}: {cents} cents", index + 1))
        .collect();

    let refunded_ids: Vec<u32> = orders
        .iter()
        .filter(|order| order.refunded)
        .map(|order| order.id)
        .collect();

    println!("paid orders: {count}, net: {net_cents} cents, average: {average_cents:.2} cents");
    println!("top customers: {top:?}");
    println!("refunded order ids: {refunded_ids:?}");
}