913-iterator-sum-product — Iterator Sum and Product
Tutorial
The Problem
Reducing a sequence to a single number by addition or multiplication is so common that every language provides a dedicated abstraction. Python's sum(), OCaml's List.fold_left (+) 0, Haskell's sum and product. Rust's Iterator::sum() and Iterator::product() are zero-cost abstractions over fold with the identity element built in: 0 for sum, 1 for product. They work on any numeric type implementing Sum or Product and can be chained with any adapter. Factorial, sum-of-squares, inner product, and average are all expressible as one-liners using these consumers.
🎯 Learning Outcomes
.sum() and .product() as clean alternatives to explicit .fold() calls(1..=n).product().map() with .sum() for sum-of-squares and weighted sums.sum() with .len()Code Example
fn sum_ints(nums: &[i32]) -> i32 { nums.iter().copied().sum() }
fn product_ints(nums: &[i32]) -> i32 { nums.iter().copied().product() }
fn factorial(n: u64) -> u64 { (1..=n).product() }
fn sum_of_squares(nums: &[i32]) -> i32 { nums.iter().map(|&x| x * x).sum() }Key Differences
.sum() and .product() communicate intent clearly; OCaml uses generic fold_left — both express the same computation..sum::<f64>(); OCaml's type inference usually handles it automatically.(1..=n).product() for factorial is more readable than OCaml's explicit fold with List.init; the computation is identical.List.sum — it is considered a special case of the more general fold; Rust optimizes for the common case.OCaml Approach
OCaml has no List.sum — it uses List.fold_left (+) 0 xs. List.fold_left ( * ) 1 xs for product. Factorial: let factorial n = List.fold_left ( * ) 1 (List.init n (fun i -> i + 1)). Sum of squares: List.fold_left (fun acc x -> acc + x * x) 0 xs. Average: float_of_int (List.fold_left (+) 0 xs) /. float_of_int (List.length xs). The explicit fold is more verbose but equivalent — OCaml's pipe operator makes it readable: xs |> List.fold_left (+) 0.
Full Source
#![allow(clippy::all)]
//! 274. Numeric reductions: sum() and product()
//!
//! `sum()` and `product()` fold iterators of numbers with + and * respectively.
//! They're zero-cost abstractions over `fold` with the identity element built in.
/// Sum a slice of integers — idiomatic: let the iterator trait do the work.
pub fn sum_ints(nums: &[i32]) -> i32 {
nums.iter().copied().sum()
}
/// Product of a slice of integers.
pub fn product_ints(nums: &[i32]) -> i32 {
nums.iter().copied().product()
}
/// Factorial via a range product — no explicit identity or accumulator needed.
pub fn factorial(n: u64) -> u64 {
(1..=n).product()
}
/// Sum of squares: map + sum in one expression.
pub fn sum_of_squares(nums: &[i32]) -> i32 {
nums.iter().map(|&x| x * x).sum()
}
/// Average of f64 prices using sum() then divide.
pub fn average(prices: &[f64]) -> Option<f64> {
if prices.is_empty() {
return None;
}
let total: f64 = prices.iter().copied().sum();
Some(total / prices.len() as f64)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sum_empty() {
assert_eq!(sum_ints(&[]), 0);
}
#[test]
fn test_sum_typical() {
assert_eq!(sum_ints(&[1, 2, 3, 4, 5]), 15);
}
#[test]
fn test_product_empty() {
// product of empty = multiplicative identity = 1
assert_eq!(product_ints(&[]), 1);
}
#[test]
fn test_product_typical() {
assert_eq!(product_ints(&[1, 2, 3, 4, 5]), 120);
}
#[test]
fn test_factorial_zero() {
assert_eq!(factorial(0), 1);
}
#[test]
fn test_factorial_five() {
assert_eq!(factorial(5), 120);
}
#[test]
fn test_factorial_ten() {
assert_eq!(factorial(10), 3_628_800);
}
#[test]
fn test_sum_of_squares() {
assert_eq!(sum_of_squares(&[1, 2, 3, 4, 5]), 55);
}
#[test]
fn test_average_empty() {
assert_eq!(average(&[]), None);
}
#[test]
fn test_average_prices() {
let prices = [9.99f64, 14.50, 3.75, 22.00];
let avg = average(&prices).unwrap();
assert!((avg - 12.56).abs() < 0.01);
}
}#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sum_empty() {
assert_eq!(sum_ints(&[]), 0);
}
#[test]
fn test_sum_typical() {
assert_eq!(sum_ints(&[1, 2, 3, 4, 5]), 15);
}
#[test]
fn test_product_empty() {
// product of empty = multiplicative identity = 1
assert_eq!(product_ints(&[]), 1);
}
#[test]
fn test_product_typical() {
assert_eq!(product_ints(&[1, 2, 3, 4, 5]), 120);
}
#[test]
fn test_factorial_zero() {
assert_eq!(factorial(0), 1);
}
#[test]
fn test_factorial_five() {
assert_eq!(factorial(5), 120);
}
#[test]
fn test_factorial_ten() {
assert_eq!(factorial(10), 3_628_800);
}
#[test]
fn test_sum_of_squares() {
assert_eq!(sum_of_squares(&[1, 2, 3, 4, 5]), 55);
}
#[test]
fn test_average_empty() {
assert_eq!(average(&[]), None);
}
#[test]
fn test_average_prices() {
let prices = [9.99f64, 14.50, 3.75, 22.00];
let avg = average(&prices).unwrap();
assert!((avg - 12.56).abs() < 0.01);
}
}
Deep Comparison
OCaml vs Rust: Numeric Reductions — sum() and product()
Side-by-Side Code
OCaml
let sum lst = List.fold_left (+) 0 lst
let product lst = List.fold_left ( * ) 1 lst
let factorial n = product (List.init n (fun i -> i + 1))
let sum_squares nums = sum (List.map (fun x -> x * x) nums)
Rust (idiomatic)
fn sum_ints(nums: &[i32]) -> i32 { nums.iter().copied().sum() }
fn product_ints(nums: &[i32]) -> i32 { nums.iter().copied().product() }
fn factorial(n: u64) -> u64 { (1..=n).product() }
fn sum_of_squares(nums: &[i32]) -> i32 { nums.iter().map(|&x| x * x).sum() }
Rust (explicit fold — shows the identity)
fn sum_fold(nums: &[i32]) -> i32 {
nums.iter().copied().fold(0, |acc, x| acc + x)
}
fn product_fold(nums: &[i32]) -> i32 {
nums.iter().copied().fold(1, |acc, x| acc * x)
}
Type Signatures
| Concept | OCaml | Rust |
|---|---|---|
| Sum function | val sum : int list -> int | fn sum_ints(nums: &[i32]) -> i32 |
| Product function | val product : int list -> int | fn product_ints(nums: &[i32]) -> i32 |
| Fold with identity | List.fold_left (+) 0 lst | iter.fold(0, \|acc, x\| acc + x) |
| Idiomatic shorthand | — | .sum() / .product() |
| Generic bound | polymorphic + / * | T: Sum / T: Product |
Key Insights
List.fold_left (+) 0 every time; Rust's .sum() and .product() name the operation and hide the identity element, making call-sites self-documenting.sum() and product() compile down to the same machine code as the explicit fold — the abstraction is pure syntax sugar with no runtime overhead.Sum and Product traits, so the same .sum() call works on i32, f64, u64, and even Option<T> — the type is resolved at compile time with no dynamic dispatch.1..=n) implement Iterator, so (1..=n).product() expresses factorial without building an intermediate list — OCaml needs List.init to create that list first.sum([]) = 0, product([]) = 1. Rust's trait implementations encode this contract; OCaml's fold_left makes it the caller's responsibility to pass the right seed.When to Use Each Style
**Use .sum() / .product() when:** you want readable, intent-revealing code and the operation is a straightforward numeric reduction — the common case.
**Use .fold() when:** the identity or combining function is non-standard (e.g., fold(f64::NEG_INFINITY, f64::max) for max) or when you need to carry extra state alongside the accumulator.
Exercises
harmonic_sum(n: u64) -> f64 = 1 + 1/2 + 1/3 + ... + 1/n using .map() and .sum().weighted_average(values: &[f64], weights: &[f64]) -> Option<f64> using zip, map, and sum.(1..n).fold() with state (a, b) and compare readability with an explicit loop.