070 — Scan Left (Running Accumulation)
Tutorial
The Problem
scan_left (Haskell: scanl, OCaml: List.fold_left with output collection) produces all intermediate values of a fold, not just the final result. Where fold([1,2,3,4], +, 0) gives 10, scan_left([1,2,3,4], +, 0) gives [0, 1, 3, 6, 10] — every prefix sum including the initial accumulator. This is the "running total" operation.
Running totals appear throughout practical computing. Financial ledgers track a running balance alongside each transaction, enabling audit trails. Prefix sum arrays — the canonical scan_left application — allow O(1) range sum queries: sum(i..j) = prefix[j] - prefix[i], making them a standard data structure in competitive programming and database query engines. Exponential moving averages in signal processing and real-time analytics compute as a scan over time-series data. Compilers accumulate struct field offset tables by scanning over field sizes. Any computation that needs to observe the fold at every step, not just at the end, uses scan.
🎯 Learning Outcomes
.scan(init, |acc, &x| { *acc = ...; Some(*acc) }) for running accumulation with mutable statescan_left<T: Clone, F>(init: T, v: &[T], f: F) -> Vec<T> that mirrors OCaml's patternCode Example
#![allow(clippy::all)]
// 070: Scan Left — running accumulation
// Approach 1: Using .scan() iterator
fn running_sum(v: &[i32]) -> Vec<i32> {
let mut result = vec![0];
result.extend(v.iter().scan(0, |state, &x| {
*state += x;
Some(*state)
}));
result
}
// Approach 2: Manual scan function
fn scan_left<T: Clone, F>(init: T, v: &[T], f: F) -> Vec<T>
where
F: Fn(&T, &T) -> T,
{
let mut result = vec![init.clone()];
let mut acc = init;
for x in v {
acc = f(&acc, x);
result.push(acc.clone());
}
result
}
fn running_product(v: &[i32]) -> Vec<i32> {
scan_left(1, v, |a, b| a * b)
}
// Approach 3: Running max
fn running_max(v: &[i32]) -> Vec<i32> {
if v.is_empty() {
return vec![];
}
let mut result = vec![v[0]];
let mut current_max = v[0];
for &x in &v[1..] {
current_max = current_max.max(x);
result.push(current_max);
}
result
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_running_sum() {
assert_eq!(running_sum(&[1, 2, 3, 4]), vec![0, 1, 3, 6, 10]);
assert_eq!(running_sum(&[]), vec![0]);
}
#[test]
fn test_running_product() {
assert_eq!(running_product(&[1, 2, 3, 4]), vec![1, 1, 2, 6, 24]);
}
#[test]
fn test_scan_left() {
assert_eq!(scan_left(0, &vec![1, 2, 3], |a, b| a + b), vec![0, 1, 3, 6]);
}
#[test]
fn test_running_max() {
assert_eq!(running_max(&[3, 1, 4, 1, 5, 9]), vec![3, 3, 4, 4, 5, 9]);
assert_eq!(running_max(&[]), Vec::<i32>::new());
}
}Key Differences
.scan() in stdlib**: Rust's Iterator::scan is built in — providing (0..n).scan(0, |acc, x| { *acc += x; Some(*acc) }). OCaml has no stdlib scan; it must be manually implemented.&mut state and can modify it. OCaml's functional approach passes the new accumulator as a recursive argument.scan_left(f, init, list) includes init as the first element. Rust's .scan() does NOT include the initial value — it starts with the result of applying f to the first element. Account for this difference.(0..=n).scan(0i32, |s, x| { *s += x; Some(*s) }).collect::<Vec<_>>() builds a prefix sum array for O(1) range queries: sum(i..j) = prefix[j] - prefix[i].OCaml Approach
OCaml's standard library List.fold_left computes only the final result. For scan, you define it manually:
let scan_left f init lst =
let rec aux acc lst result =
match lst with
| [] -> List.rev result
| x :: t ->
let acc' = f acc x in
aux acc' t (acc' :: result)
in
init :: aux init lst []
This is tail-recursive via the accumulator result list, reversed at the end. OCaml 4.11 added List.fold_left_map which produces a transformed list alongside the accumulator, partially overlapping with scan for element-wise transforms but not providing running intermediates directly.
Full Source
#![allow(clippy::all)]
// 070: Scan Left — running accumulation
// Approach 1: Using .scan() iterator
fn running_sum(v: &[i32]) -> Vec<i32> {
let mut result = vec![0];
result.extend(v.iter().scan(0, |state, &x| {
*state += x;
Some(*state)
}));
result
}
// Approach 2: Manual scan function
fn scan_left<T: Clone, F>(init: T, v: &[T], f: F) -> Vec<T>
where
F: Fn(&T, &T) -> T,
{
let mut result = vec![init.clone()];
let mut acc = init;
for x in v {
acc = f(&acc, x);
result.push(acc.clone());
}
result
}
fn running_product(v: &[i32]) -> Vec<i32> {
scan_left(1, v, |a, b| a * b)
}
// Approach 3: Running max
fn running_max(v: &[i32]) -> Vec<i32> {
if v.is_empty() {
return vec![];
}
let mut result = vec![v[0]];
let mut current_max = v[0];
for &x in &v[1..] {
current_max = current_max.max(x);
result.push(current_max);
}
result
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_running_sum() {
assert_eq!(running_sum(&[1, 2, 3, 4]), vec![0, 1, 3, 6, 10]);
assert_eq!(running_sum(&[]), vec![0]);
}
#[test]
fn test_running_product() {
assert_eq!(running_product(&[1, 2, 3, 4]), vec![1, 1, 2, 6, 24]);
}
#[test]
fn test_scan_left() {
assert_eq!(scan_left(0, &vec![1, 2, 3], |a, b| a + b), vec![0, 1, 3, 6]);
}
#[test]
fn test_running_max() {
assert_eq!(running_max(&[3, 1, 4, 1, 5, 9]), vec![3, 3, 4, 4, 5, 9]);
assert_eq!(running_max(&[]), Vec::<i32>::new());
}
}#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_running_sum() {
assert_eq!(running_sum(&[1, 2, 3, 4]), vec![0, 1, 3, 6, 10]);
assert_eq!(running_sum(&[]), vec![0]);
}
#[test]
fn test_running_product() {
assert_eq!(running_product(&[1, 2, 3, 4]), vec![1, 1, 2, 6, 24]);
}
#[test]
fn test_scan_left() {
assert_eq!(scan_left(0, &vec![1, 2, 3], |a, b| a + b), vec![0, 1, 3, 6]);
}
#[test]
fn test_running_max() {
assert_eq!(running_max(&[3, 1, 4, 1, 5, 9]), vec![3, 3, 4, 4, 5, 9]);
assert_eq!(running_max(&[]), Vec::<i32>::new());
}
}
Deep Comparison
Core Insight
Scan is fold that emits every intermediate accumulator value. scan_left [1;2;3] (+) 0 gives [0;1;3;6] — the running sum. It's the functional equivalent of a cumulative sum.
OCaml Approach
scan_left — implement with fold or recursionList.fold_left adapted to collect intermediatesRust Approach
.scan(state, |st, x| ...) — stateful iterator adapterOption to allow early terminationComparison Table
| Feature | OCaml | Rust |
|---|---|---|
| Built-in | No | .scan() iterator adapter |
| Result | List of accumulators | Iterator of values |
| State | Accumulator in recursion | Mutable state in closure |
Exercises
[3, 1, 4, 1, 5, 9, 2, 6]. Write range_sum(prefix: &[i32], i: usize, j: usize) -> i32 that computes the sum of elements from index i to j in O(1).running_mean(v: &[f64]) -> Vec<f64> where result[i] is the mean of v[0..=i]. Use scan with a (sum, count) state tuple.v[i-k+1..=i]. This requires a deque, not just scan — research the monotone deque approach.