AI Library · 知识库

# 聚合:汇总订单金额

学习目标:掌握集合累加(Fold/Reduce)与统计聚合,理解金额的整数表示与空集合聚合的边界处理。

前置知识与范围:基本数值计算、列表与循环。本篇聚焦内存中金融订单数据的求和与安全均值计算。

# 问题与边界

给定一组订单记录,每笔订单包含标识和以“分”为单位的金额(amount_cents,使用定点整数杜绝二进制浮点舍入误差):

# 关键差异

语言 求和方式 空集合均值表达 溢出保护考虑
Rust .iter().map(...).sum::<i64>() 返回 Option<f64> 显式指定宽类型 i64 防溢出
Go 命令式循环累加 (int64, error) 或 (float64, bool) 手动选择 int64
Java .stream().mapToLong(...).sum() .average() 返回 OptionalDouble 原生内置专用基本类型流防止装箱
JavaScript orders.reduce((acc, o) => acc + o.amountCents, 0) 显式长度判断 len === 0 ? null : sum / len Number 超过 2^53-1 需用 BigInt
TypeScript 沿用 JS reduce number | null 严格返回类型标注
Kotlin orders.sumOf { it.amountCents } 扩展函数自定义或 if (isEmpty()) null sumOf 返回 Long
Python 原生 sum(o.amount_cents for o in orders) sum / len(orders) if orders else None 任意精度整数,不会溢出
C++ <numeric> 中 std::accumulate orders.empty() ? std::nullopt : ... 显式指定初值类型(如 int64_t{0})

# 各语言实现

# Rust

Ruststruct Order {
    id: u64,
    amount_cents: i64,
}

fn summarize_orders(orders: &[Order]) -> (i64, Option<f64>) {
    let total: i64 = orders.iter().map(|o| o.amount_cents).sum();
    let avg = if orders.is_empty() {
        None
    } else {
        Some(total as f64 / orders.len() as f64)
    };
    (total, avg)
}

fn main() {
    let orders = [
        Order { id: 1, amount_cents: 1500 },
        Order { id: 2, amount_cents: 2500 },
        Order { id: 3, amount_cents: 500 },
    ];

    let (total, avg) = summarize_orders(&orders);
    assert_eq!(total, 4500);
    assert_eq!(avg, Some(1500.0));

    let (empty_total, empty_avg) = summarize_orders(&[]);
    assert_eq!(empty_total, 0);
    assert_eq!(empty_avg, None);

    println!("total: {total}, avg: {}", avg.unwrap());
}

Rust 的 sum 泛型方法自动适配目标类型;空集合求均值使用 Option<f64> 防除以零。

# Go

Gopackage main

import (
	"errors"
	"fmt"
)

type Order struct {
	ID          uint64
	AmountCents int64
}

func SummarizeOrders(orders []Order) (int64, float64, error) {
	if len(orders) == 0 {
		return 0, 0, errors.New("empty orders")
	}
	var total int64
	for _, o := range orders {
		total += o.AmountCents
	}
	return total, float64(total) / float64(len(orders)), nil
}

func main() {
	orders := []Order{
		{ID: 1, AmountCents: 1500},
		{ID: 2, AmountCents: 2500},
		{ID: 3, AmountCents: 500},
	}

	total, avg, err := SummarizeOrders(orders)
	if err != nil || total != 4500 || avg != 1500.0 {
		panic("mismatch")
	}

	if _, _, err := SummarizeOrders(nil); err == nil {
		panic("expected error on empty")
	}

	fmt.Printf("total: %d, avg: %.2f\n", total, avg)
}

Go 采用标准的错误返回模式处理空输入,累加逻辑简单透明。

# Java

Javaimport java.util.List;
import java.util.OptionalDouble;

public class Main {
    public record Order(long id, long amountCents) {}

    public static void main(String[] args) {
        List<Order> orders = List.of(
            new Order(1, 1500),
            new Order(2, 2500),
            new Order(3, 500)
        );

        long total = orders.stream().mapToLong(Order::amountCents).sum();
        OptionalDouble avg = orders.stream().mapToLong(Order::amountCents).average();

        assert total == 4500;
        assert avg.isPresent() && avg.getAsDouble() == 1500.0;

        assert List.<Order>of().stream().mapToLong(Order::amountCents).average().isEmpty();

        System.out.println("total: " + total + ", avg: " + avg.getAsDouble());
    }
}

Java 标准库的 LongStream.average() 原生返回 OptionalDouble,安全规避了空集合除以零。

# JavaScript

JavaScriptconst assert = {
  equal: (a, b) => { if (a !== b) throw new Error(`${a} !== ${b}`); },
  deepEqual: (a, b) => { if (JSON.stringify(a) !== JSON.stringify(b)) throw new Error(`${JSON.stringify(a)} !== ${JSON.stringify(b)}`); },
  throws: (fn, errType) => {
    try { fn(); throw new Error("expected throw"); } catch (e) {
      if (e.message === "expected throw") throw e;
      if (errType && !(e instanceof errType)) throw e;
    }
  }
};

function summarizeOrders(orders) {
  const total = orders.reduce((sum, o) => sum + o.amountCents, 0);
  const avg = orders.length === 0 ? null : total / orders.length;
  return { total, avg };
}

const orders = [
  { id: 1, amountCents: 1500 },
  { id: 2, amountCents: 2500 },
  { id: 3, amountCents: 500 },
];

const { total, avg } = summarizeOrders(orders);
assert.equal(total, 4500);
assert.equal(avg, 1500);

const empty = summarizeOrders([]);
assert.equal(empty.total, 0);
assert.equal(empty.avg, null);

console.log(`total: ${total}, avg: ${avg}`);

JavaScript 的 Array.prototype.reduce 必须传递显式初始值 0,否则空数组调用会抛出异常。

# TypeScript

TypeScriptconst assert = {
  equal: (a: unknown, b: unknown) => { if (a !== b) throw new Error(`${a} !== ${b}`); },
  deepEqual: (a: unknown, b: unknown) => { if (JSON.stringify(a) !== JSON.stringify(b)) throw new Error(`${JSON.stringify(a)} !== ${JSON.stringify(b)}`); },
  throws: (fn: () => void, errType?: unknown) => {
    try { fn(); throw new Error("expected throw"); } catch (e: unknown) {
      if (e instanceof Error && e.message === "expected throw") throw e;
      if (typeof errType === "function" && !(e instanceof errType)) throw e;
    }
  }
};

interface Order {
  readonly id: number;
  readonly amountCents: number;
}

interface Summary {
  readonly total: number;
  readonly avg: number | null;
}

function summarizeOrders(orders: readonly Order[]): Summary {
  const total = orders.reduce((sum, o) => sum + o.amountCents, 0);
  const avg = orders.length === 0 ? null : total / orders.length;
  return { total, avg };
}

const orders: readonly Order[] = [
  { id: 1, amountCents: 1500 },
  { id: 2, amountCents: 2500 },
  { id: 3, amountCents: 500 },
];

const { total, avg } = summarizeOrders(orders);
assert.equal(total, 4500);
assert.equal(avg, 1500);

console.log(`total: ${total}, avg: ${avg}`);

TypeScript 返回类型标注为 number | null,强制调用方进行空判断。

# Kotlin

Kotlindata class Order(val id: Long, val amountCents: Long)

fun main() {
    val orders = listOf(
        Order(1, 1500),
        Order(2, 2500),
        Order(3, 500)
    )

    val total = orders.sumOf { it.amountCents }
    val avg = if (orders.isEmpty()) null else total.toDouble() / orders.size

    check(total == 4500L)
    check(avg == 1500.0)

    println("total: $total, avg: $avg")
}

Kotlin 标准库提供的 sumOf 可以直接针对长整型求和,返回 Long。

# Python

Pythonfrom typing import NamedTuple, List, Optional, Tuple

class Order(NamedTuple):
    id: int
    amount_cents: int

def summarize_orders(orders: List[Order]) -> Tuple[int, Optional[float]]:
    total = sum(o.amount_cents for o in orders)
    avg = total / len(orders) if orders else None
    return total, avg

if __name__ == "__main__":
    orders = [
        Order(1, 1500),
        Order(2, 2500),
        Order(3, 500),
    ]

    total, avg = summarize_orders(orders)
    assert total == 4500
    assert avg == 1500.0

    empty_total, empty_avg = summarize_orders([])
    assert empty_total == 0
    assert empty_avg is None

    print(f"total: {total}, avg: {avg}")

Python 的生成器表达式配合内置 sum() 极度简洁且支持大整数自动防溢出。

# C++

C++#include <iostream>
#include <vector>
#include <numeric>
#include <optional>
#include <cstdint>
#include <cassert>

struct Order {
    uint64_t id;
    int64_t amount_cents;
};

std::pair<int64_t, std::optional<double>> summarize_orders(const std::vector<Order>& orders) {
    int64_t total = std::accumulate(orders.begin(), orders.end(), int64_t{0},
        [](int64_t sum, const Order& o) {
            return sum + o.amount_cents;
        });

    std::optional<double> avg = orders.empty()
        ? std::nullopt
        : std::make_optional(static_cast<double>(total) / orders.size());

    return {total, avg};
}

int main() {
    std::vector<Order> orders = {
        {1, 1500},
        {2, 2500},
        {3, 500}
    };

    auto [total, avg] = summarize_orders(orders);
    assert(total == 4500);
    assert(avg.has_value() && *avg == 1500.0);

    auto [empty_total, empty_avg] = summarize_orders({});
    assert(empty_total == 0);
    assert(!empty_avg.has_value());

    std::cout << "total: " << total << ", avg: " << *avg << "\n";
}

C++ 中的 std::accumulate 第三参数决定累加类型,切记显式写 int64_t{0} 而非裸 0(会导致按 32 位 int 累加溢出)。

# 迁移误区与适用限制

# 运行与验证

语言版本基线见示例基线。 所有程序运行后控制台输出均为:

纯文本total: 4500, avg: 1500

总和、平均值及空集合边界测试均通过。

# 相关阅读