๐Ÿฆ€ Functional Rust
๐ŸŽฌ How Rust Iterators Work Lazy evaluation, chaining, collect(), and zero-cost abstractions.
๐Ÿ“ Text version (for readers / accessibility)

โ€ข Iterators are lazy โ€” .map(), .filter(), .take() build a chain but do no work until consumed

โ€ข .collect() triggers evaluation, transforming the chain into a Vec, HashMap, or other collection

โ€ข Zero-cost abstraction: iterator chains compile to the same machine code as hand-written loops

โ€ข .iter() borrows, .into_iter() consumes, .iter_mut() borrows mutably

โ€ข Chaining replaces nested loops with a readable, composable pipeline

784. Fibonacci: Memoisation vs Tabulation DP

Difficulty: 3 Level: Intermediate Four implementations of Fibonacci: naive, top-down memoisation, bottom-up tabulation, space-optimised, and lazy iterator โ€” showing the full DP spectrum in Rust.

The Problem This Solves

Fibonacci is the canonical DP teaching example, but its real lesson isn't the numbers โ€” it's the two fundamental approaches to dynamic programming: top-down (memoisation) and bottom-up (tabulation). Every DP problem you'll ever face can be attacked with either strategy, and Fibonacci lets you understand both without the complexity of a harder problem. In production code you'd use the O(1)-space optimised version for simple sequences, but memoisation and tabulation patterns appear everywhere: caching expensive recursive computations in parsers, compilers, and planners; building DP tables for sequence alignment and shortest-path algorithms; and structuring lazy evaluation pipelines. The iterator variant shows a distinctly Rust idiom: infinite lazy sequences via `impl Iterator`, letting consumers pull only what they need.

The Intuition

The naive recursion recomputes `fib(n-2)` exponentially many times. Both DP approaches avoid this by storing previously computed results: memoisation caches on the way down the recursion tree; tabulation fills a table on the way up from the base cases. In OCaml you'd reach for a recursive solution with a `Hashtbl` for memoisation; in Rust, the tabulation approach using a `Vec` is usually preferable because it's cache-friendly and avoids recursive call overhead.

How It Works in Rust

// 1. Naive โ€” O(2^n): exponential recomputation, only useful as a baseline
pub fn fib_naive(n: u32) -> u64 {
 match n { 0 => 0, 1 => 1, n => fib_naive(n-1) + fib_naive(n-2) }
}

// 2. Top-down memoisation โ€” O(n) time, O(n) space
//    HashMap caches results; each subproblem solved once
pub fn fib_memo(n: u64) -> u64 {
 fib_memo_inner(n, &mut HashMap::new())
}

// 3. Bottom-up tabulation โ€” O(n) time, O(n) space
//    Fill Vec left-to-right; no recursion, better cache locality
pub fn fib_tab(n: usize) -> u64 {
 let mut t = vec![0u64; n + 1];
 t[1] = 1;
 for i in 2..=n { t[i] = t[i-1] + t[i-2]; }
 t[n]
}

// 4. Space-optimised โ€” O(1) space: only keep last two values
pub fn fib_opt(n: usize) -> u64 {
 let (mut a, mut b) = (0u64, 1u64);
 for _ in 2..=n { let c = a + b; a = b; b = c; }
 b
}

// 5. Iterator (infinite lazy stream) โ€” idiomatic Rust
pub struct FibIter { a: u64, b: u64 }
impl Iterator for FibIter {
 type Item = u64;
 fn next(&mut self) -> Option<u64> {
     let val = self.a;
     let next = self.a + self.b;
     self.a = self.b; self.b = next;
     Some(val)
 }
}
// Usage: FibIter::new().find(|&n| n > 1_000_000)
The benchmark shows naive is ~100ร— slower than tabulation at `fib(35)`, and memoisation is comparable to tabulation but with HashMap overhead.

What This Unlocks

Key Differences

ConceptOCamlRust
Memoisation`Hashtbl.find_opt` + `Hashtbl.add``HashMap::get` + `HashMap::insert`
Recursion depthFine (tail-rec optimised, or just works)Stack overflow risk at large n; prefer iterative
Lazy stream`Stream.from` / `Seq.unfold``impl Iterator` with mutable state
Space-opt loopPattern match on tupleDestructuring `let (mut a, mut b)`
Tabulation`Array.init``vec![0u64; n+1]` then indexed mutation
// 784. Fibonacci: Memoisation vs Tabulation DP
// Four approaches: naive, top-down memo, bottom-up tabulation, space-optimised

use std::collections::HashMap;

// โ”€โ”€ 1. Naive recursion โ€” O(2^n), for comparison only โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

pub fn fib_naive(n: u32) -> u64 {
    match n { 0 => 0, 1 => 1, n => fib_naive(n-1) + fib_naive(n-2) }
}

// โ”€โ”€ 2. Top-down memoisation โ€” O(n) time, O(n) space โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

pub fn fib_memo_inner(n: u64, memo: &mut HashMap<u64, u64>) -> u64 {
    if n <= 1 { return n; }
    if let Some(&v) = memo.get(&n) { return v; }
    let v = fib_memo_inner(n-1, memo) + fib_memo_inner(n-2, memo);
    memo.insert(n, v);
    v
}

pub fn fib_memo(n: u64) -> u64 {
    fib_memo_inner(n, &mut HashMap::new())
}

// โ”€โ”€ 3. Bottom-up tabulation โ€” O(n) time, O(n) space โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

pub fn fib_tab(n: usize) -> u64 {
    if n == 0 { return 0; }
    let mut t = vec![0u64; n + 1];
    t[1] = 1;
    for i in 2..=n {
        t[i] = t[i-1] + t[i-2];
    }
    t[n]
}

// โ”€โ”€ 4. Space-optimised โ€” O(1) space (two variables) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

pub fn fib_opt(n: usize) -> u64 {
    match n { 0 => return 0, 1 => return 1, _ => {} }
    let (mut a, mut b) = (0u64, 1u64);
    for _ in 2..=n {
        let c = a + b;
        a = b;
        b = c;
    }
    b
}

// โ”€โ”€ 5. Iterator-based (infinite stream, Rust idiom) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

pub struct FibIter { a: u64, b: u64 }
impl FibIter {
    pub fn new() -> Self { Self { a: 0, b: 1 } }
}
impl Iterator for FibIter {
    type Item = u64;
    fn next(&mut self) -> Option<u64> {
        let val = self.a;
        let next = self.a + self.b;
        self.a = self.b;
        self.b = next;
        Some(val)
    }
}

// โ”€โ”€ Benchmark comparison โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

use std::time::Instant;

fn time_it<F: Fn() -> u64>(name: &str, f: F) {
    let t = Instant::now();
    let result = f();
    let elapsed = t.elapsed();
    println!("{name:25} = {result:20}  [{elapsed:?}]");
}

fn main() {
    println!("=== Correctness check (all should agree) ===");
    for n in [0, 1, 2, 10, 30, 50] {
        let a = fib_memo(n as u64);
        let b = fib_tab(n);
        let c = fib_opt(n);
        assert_eq!(a, b as u64);
        assert_eq!(a, c as u64);
        println!("  fib({n:2}) = {a}");
    }

    println!("\n=== Performance ===");
    time_it("fib_naive(35)",  || fib_naive(35));
    time_it("fib_memo(1000)", || fib_memo(1000) % 1_000_000_007);
    time_it("fib_tab(1000)",  || fib_tab(1000)  % 1_000_000_007);
    time_it("fib_opt(1000)",  || fib_opt(1000)  % 1_000_000_007);

    println!("\n=== Iterator (first 15 Fibonacci numbers) ===");
    let fibs: Vec<u64> = FibIter::new().take(15).collect();
    println!("{fibs:?}");

    println!("\n=== First Fibonacci > 1,000,000 ===");
    let big = FibIter::new().find(|&n| n > 1_000_000).unwrap();
    println!("{big}");
}

#[cfg(test)]
mod tests {
    use super::*;

    fn all_agree(n: usize) {
        let a = fib_memo(n as u64);
        let b = fib_tab(n) as u64;
        let c = fib_opt(n) as u64;
        assert_eq!(a, b, "memo vs tab at n={n}");
        assert_eq!(b, c, "tab vs opt at n={n}");
    }

    #[test]
    fn base_cases() { all_agree(0); all_agree(1); }
    #[test]
    fn small_values() { for i in 2..=20 { all_agree(i); } }
    #[test]
    fn larger_values() { all_agree(50); all_agree(80); }

    #[test]
    fn naive_matches_opt() {
        for i in 0..15 { assert_eq!(fib_naive(i as u32), fib_opt(i)); }
    }

    #[test]
    fn iter_correct() {
        let v: Vec<u64> = FibIter::new().take(8).collect();
        assert_eq!(v, vec![0, 1, 1, 2, 3, 5, 8, 13]);
    }
}
(* Fibonacci: memoisation vs tabulation in OCaml
   OCaml's strength: elegant recursion with memoization via Hashtbl *)

(* 1. Naive recursion โ€” exponential O(2^n) *)
let rec fib_naive n =
  if n <= 1 then n else fib_naive (n-1) + fib_naive (n-2)

(* 2. Top-down memoisation โ€” O(n) time, O(n) space *)
let memo : (int, int) Hashtbl.t = Hashtbl.create 100

let rec fib_memo n =
  if n <= 1 then n
  else match Hashtbl.find_opt memo n with
  | Some v -> v
  | None ->
    let v = fib_memo (n-1) + fib_memo (n-2) in
    Hashtbl.replace memo n v;
    v

(* 3. Bottom-up tabulation โ€” O(n) time, O(n) space *)
let fib_tab n =
  if n <= 1 then n
  else begin
    let t = Array.make (n+1) 0 in
    t.(1) <- 1;
    for i = 2 to n do
      t.(i) <- t.(i-1) + t.(i-2)
    done;
    t.(n)
  end

(* 4. Space-optimised โ€” O(1) space *)
let fib_opt n =
  if n = 0 then 0
  else begin
    let a = ref 0 and b = ref 1 in
    for _ = 2 to n do
      let c = !a + !b in
      a := !b; b := c
    done;
    !b
  end

let () =
  Printf.printf "%-25s" "naive fib(10..20):";
  for i = 10 to 20 do Printf.printf " %d" (fib_naive i) done; print_newline ();
  Printf.printf "%-25s" "memo  fib(50):";
  Printf.printf " %d\n" (fib_memo 50);
  Printf.printf "%-25s" "tab   fib(50):";
  Printf.printf " %d\n" (fib_tab  50);
  Printf.printf "%-25s" "opt   fib(50):";
  Printf.printf " %d\n" (fib_opt  50)