๐Ÿฆ€ Functional Rust

825: Prime Factorization

Difficulty: 4 Level: Advanced Factor any 64-bit integer: trial division for small factors, Pollard's rho for large semi-primes โ€” combining O(โˆšn) and O(n^(1/4)) into a practical factorizer.

The Problem This Solves

Cryptography depends on the hardness of factoring large numbers: RSA's security relies on the fact that given n = p ร— q where p, q are large primes, finding p and q is computationally infeasible. Implementing a factorizer teaches you why RSA is hard โ€” and exactly where its security boundary lies. For competitive programming, factorizing numbers up to 10^18 appears constantly in problems involving Euler's totient function, divisor counts, and multiplicative functions. Trial division alone fails for large primes and semi-primes. Pollard's rho is the practical algorithm: it finds a factor of n in O(n^(1/4)) expected time using a pseudo-random walk and Floyd's cycle detection. The combined approach (trial division for small factors, Pollard's rho for the residue, Miller-Rabin to confirm primality) is how real-world factorizers work โ€” including Mathematica's `FactorInteger` for moderate-sized inputs.

The Intuition

Trial division: divide by 2, then odd numbers up to โˆšn. Any remaining factor > โˆšn must be prime. This handles all numbers efficiently when the smallest prime factor is small (which it often is โ€” about 30% of random numbers are divisible by 2, 3, or 5). Pollard's rho: the sequence `x_{n+1} = (x_nยฒ + c) mod n` eventually cycles (Birthday Paradox โ€” after ~O(n^(1/4)) steps, two sequence values share a factor with n). Floyd's tortoise-and-hare detects the cycle. The GCD of the difference with n reveals the factor. Uses O(1) space beyond the sequence variables. In Rust: `u64` wrapping arithmetic handles the pseudo-random walk; `u64::abs_diff` avoids signed overflow.

How It Works in Rust

fn gcd(a: u64, b: u64) -> u64 {
 if b == 0 { a } else { gcd(b, a % b) }
}

// Trial division: O(โˆšn), handles small factors efficiently
fn factorize(mut n: u64) -> Vec<(u64, u32)> {
 let mut factors = Vec::new();
 if n % 2 == 0 {
     let mut exp = 0u32;
     while n % 2 == 0 { exp += 1; n /= 2; }
     factors.push((2, exp));
 }
 let mut d = 3u64;
 while d * d <= n {
     if n % d == 0 {
         let mut exp = 0u32;
         while n % d == 0 { exp += 1; n /= d; }
         factors.push((d, exp));
     }
     d += 2;
 }
 if n > 1 { factors.push((n, 1)); }  // Remaining factor is prime
 factors
}

// Pollard's rho: O(n^(1/4)) expected, for large semi-primes
fn pollard_rho(n: u64) -> u64 {
 if n % 2 == 0 { return 2; }
 let mut c = 1u64;
 loop {
     let (mut x, mut y, mut d) = (2u64, 2u64, 1u64);
     while d == 1 {
         // Pseudo-random walk: f(x) = (xยฒ + c) mod n
         x = (x.wrapping_mul(x).wrapping_add(c)) % n;
         y = (y.wrapping_mul(y).wrapping_add(c)) % n;  // Tortoise step
         y = (y.wrapping_mul(y).wrapping_add(c)) % n;  // Hare double-steps
         d = gcd(x.abs_diff(y), n);                    // GCD reveals factor
     }
     if d != n { return d; }
     c += 1;  // Retry with different constant
 }
}
`abs_diff` (stable since Rust 1.60) cleanly handles unsigned subtraction without overflow โ€” replaces the `(a as i64 - b as i64).unsigned_abs()` pattern from earlier Rust.

What This Unlocks

Key Differences

ConceptOCamlRust
GCD one-liner`let rec gcd a b = if b=0 then a else gcd b (a mod b)``fn gcd(a:u64,b:u64)->u64{if b==0{a}else{gcd(b,a%b)}}`
64-bit arithmetic`Int64` or `nativeint``u64` natively; no wrapper needed
Unsigned subtract`abs (a - b)` with care`a.abs_diff(b)` โ€” safe on `u64`
Wrapping mul`Int64.rem (Int64.mul x x) n``x.wrapping_mul(x) % n`
Recursive styleNatural tail recursionIterative loop to avoid stack
/// Prime Factorization: trial division + Pollard's rho concept.
/// Trial division handles small factors; Pollard's rho concept for large n.

/// GCD (Euclidean) โ€” mirrors OCaml's elegant one-liner.
fn gcd(a: u64, b: u64) -> u64 {
    if b == 0 { a } else { gcd(b, a % b) }
}

/// Trial division: returns sorted list of (prime, exponent) pairs.
fn factorize(mut n: u64) -> Vec<(u64, u32)> {
    let mut factors = Vec::new();
    // Factor out 2
    if n % 2 == 0 {
        let mut exp = 0u32;
        while n % 2 == 0 { exp += 1; n /= 2; }
        factors.push((2, exp));
    }
    // Odd factors from 3
    let mut d = 3u64;
    while d * d <= n {
        if n % d == 0 {
            let mut exp = 0u32;
            while n % d == 0 { exp += 1; n /= d; }
            factors.push((d, exp));
        }
        d += 2;
    }
    if n > 1 {
        factors.push((n, 1));
    }
    factors
}

/// Pollard's rho: find a non-trivial factor of n (n composite, n > 1).
/// Uses Floyd's cycle detection with f(x) = (xยฒ + c) mod n.
fn pollard_rho(n: u64) -> u64 {
    if n % 2 == 0 {
        return 2;
    }
    let mut c = 1u64;
    loop {
        let mut x = 2u64;
        let mut y = 2u64;
        let mut d = 1u64;
        while d == 1 {
            x = (x.wrapping_mul(x).wrapping_add(c)) % n;
            y = (y.wrapping_mul(y).wrapping_add(c)) % n;
            y = (y.wrapping_mul(y).wrapping_add(c)) % n;
            d = gcd(x.abs_diff(y), n);
        }
        if d != n {
            return d;
        }
        c += 1; // Retry
    }
}

/// Miller-Rabin primality (simple version for testing in Pollard context).
fn is_prime_trial(n: u64) -> bool {
    if n < 2 { return false; }
    if n == 2 { return true; }
    if n % 2 == 0 { return false; }
    let mut d = 3u64;
    while d * d <= n {
        if n % d == 0 { return false; }
        d += 2;
    }
    true
}

/// Full factorization using Pollard's rho for large primes.
fn factorize_full(n: u64) -> Vec<u64> {
    if n <= 1 { return vec![]; }
    if is_prime_trial(n) { return vec![n]; }
    let d = pollard_rho(n);
    let mut a = factorize_full(d);
    let mut b = factorize_full(n / d);
    a.append(&mut b);
    a.sort();
    a
}

fn format_factors(factors: &[(u64, u32)]) -> String {
    factors
        .iter()
        .map(|&(p, e)| if e == 1 { format!("{p}") } else { format!("{p}^{e}") })
        .collect::<Vec<_>>()
        .join(" ร— ")
}

fn main() {
    let tests = [12u64, 100, 360, 97, 1_234_567_890, 720_720];
    for &n in &tests {
        let f = factorize(n);
        println!("factorize({n}) = {}", format_factors(&f));
    }

    println!("\nPollard rho examples:");
    for &n in &[15u64, 35, 77, 8051, 1_000_003 * 1_000_033] {
        let factors = factorize_full(n);
        println!("  {n} = {:?}", factors);
    }
}

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

    #[test]
    fn test_factorize_12() {
        assert_eq!(factorize(12), vec![(2, 2), (3, 1)]);
    }

    #[test]
    fn test_factorize_100() {
        assert_eq!(factorize(100), vec![(2, 2), (5, 2)]);
    }

    #[test]
    fn test_factorize_prime() {
        assert_eq!(factorize(97), vec![(97, 1)]);
    }

    #[test]
    fn test_factorize_1() {
        assert_eq!(factorize(1), vec![]);
    }

    #[test]
    fn test_factorize_360() {
        // 360 = 2ยณ ร— 3ยฒ ร— 5
        assert_eq!(factorize(360), vec![(2, 3), (3, 2), (5, 1)]);
    }

    #[test]
    fn test_product_check() {
        // Verify factors multiply back to original
        for &n in &[12u64, 100, 360, 720720] {
            let factors = factorize(n);
            let product: u64 = factors.iter().map(|&(p, e)| p.pow(e)).product();
            assert_eq!(product, n, "product mismatch for {n}");
        }
    }

    #[test]
    fn test_pollard_finds_factor() {
        // 35 = 5 ร— 7
        let d = pollard_rho(35);
        assert!(d == 5 || d == 7);
    }

    #[test]
    fn test_is_prime_trial() {
        assert!(is_prime_trial(97));
        assert!(!is_prime_trial(100));
        assert!(is_prime_trial(2));
    }
}
(* Prime Factorization in OCaml โ€” trial division + Pollard rho concept *)

(* GCD โ€” the elegant one-liner *)
let rec gcd a b = if b = 0 then a else gcd b (a mod b)

(* Trial division factorization *)
let factorize (n : int) : (int * int) list =
  let factors = ref [] in
  let n = ref n in
  (* Handle factor 2 separately *)
  let cnt = ref 0 in
  while !n mod 2 = 0 do
    incr cnt; n := !n / 2
  done;
  if !cnt > 0 then factors := (2, !cnt) :: !factors;
  (* Odd factors from 3 to sqrt(n) *)
  let d = ref 3 in
  while !d * !d <= !n do
    cnt := 0;
    while !n mod !d = 0 do
      incr cnt; n := !n / !d
    done;
    if !cnt > 0 then factors := (!d, !cnt) :: !factors;
    d := !d + 2
  done;
  (* Remaining factor is prime *)
  if !n > 1 then factors := (!n, 1) :: !factors;
  List.rev !factors

(* Pollard's rho: find a non-trivial factor of n (n must be composite) *)
let pollard_rho (n : int) : int =
  if n mod 2 = 0 then 2
  else begin
    let x = ref 2 and y = ref 2 and c = ref 1 and d = ref 1 in
    while !d = 1 do
      x := (!x * !x + !c) mod n;
      y := (!y * !y + !c) mod n;
      y := (!y * !y + !c) mod n;
      d := gcd (abs (!x - !y)) n;
      if !d = n then begin
        (* Retry with different c *)
        c := !c + 1; x := 2; y := 2; d := 1
      end
    done;
    !d
  end

(* Pretty-print factorization *)
let pp_factors factors =
  String.concat " ร— " (List.map (fun (p, e) ->
    if e = 1 then string_of_int p
    else Printf.sprintf "%d^%d" p e
  ) factors)

let () =
  let tests = [12; 100; 360; 97; 1234567890; 720720] in
  List.iter (fun n ->
    let f = factorize n in
    Printf.printf "factorize(%d) = %s\n" n (pp_factors f)
  ) tests;
  (* Verify: product should equal n *)
  let n = 360 in
  let f = factorize n in
  let product = List.fold_left (fun acc (p, e) ->
    let rec pow b e = if e = 0 then 1 else b * pow b (e-1) in
    acc * pow p e
  ) 1 f in
  Printf.printf "product check: %d = %d โ†’ %b\n" n product (n = product)