ExamplesBy LevelBy TopicLearning Paths
824 Intermediate

Sieve of Eratosthenes

Functional Programming

Tutorial Video

Text description (accessibility)

This video demonstrates the "Sieve of Eratosthenes" functional Rust example. Difficulty level: Intermediate. Key concepts covered: Functional Programming. Generating all prime numbers up to N is a fundamental operation in number theory with applications in cryptography (RSA key generation requires large primes), competitive programming, and mathematical software. Key difference from OCaml: | Aspect | Rust | OCaml |

Tutorial

The Problem

Generating all prime numbers up to N is a fundamental operation in number theory with applications in cryptography (RSA key generation requires large primes), competitive programming, and mathematical software. Trial division for each number takes O(sqrt(n)) per number, giving O(n*sqrt(n)) total. The Sieve of Eratosthenes achieves O(n log log n) by marking multiples of each prime in bulk: once 2's multiples are marked, 3's multiples, 5's multiples, etc. Every composite number gets eliminated exactly once. For N up to 10^7, the sieve runs in milliseconds and uses O(n) memory. Segmented variants extend this to arbitrary ranges without holding all of 0..N in memory.

🎯 Learning Outcomes

  • • Implement the classic sieve: initialize all to true, mark multiples of each prime starting from p^2
  • • Understand why we start marking from p^2: smaller multiples were already marked by earlier primes
  • • Recognize the time complexity: each composite is marked exactly once by its smallest prime factor
  • • Learn the segmented sieve for memory-efficient prime generation for very large N
  • • Apply the sieve to number-theoretic computations: Euler's totient, smallest prime factor table
  • Code Example

    #![allow(clippy::all)]
    //! # Sieve of Eratosthenes
    pub fn sieve(n: usize) -> Vec<bool> {
        let mut is_prime = vec![true; n + 1];
        is_prime[0] = false;
        if n >= 1 {
            is_prime[1] = false;
        }
        for i in 2..=((n as f64).sqrt() as usize) {
            if is_prime[i] {
                for j in (i * i..=n).step_by(i) {
                    is_prime[j] = false;
                }
            }
        }
        is_prime
    }
    
    pub fn primes_up_to(n: usize) -> Vec<usize> {
        sieve(n)
            .iter()
            .enumerate()
            .filter_map(|(i, &b)| if b { Some(i) } else { None })
            .collect()
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
        #[test]
        fn test_sieve() {
            assert_eq!(primes_up_to(20), vec![2, 3, 5, 7, 11, 13, 17, 19]);
        }
    }

    Key Differences

    AspectRustOCaml
    Array initvec![true; n+1]Array.make (n+1) true
    Outer loopwhile p * p <= nfor p = 2 to isqrt n
    Inner loopwhile m <= n { m += p }let m = ref (p*p); while !m <= n
    Memory (bool)1 byte/bool (8x waste)1 word/bool (worse)
    Result typeVec<bool> (sieve) or Vec<usize>bool array or int list
    Segmented variantManual chunk iterationSame approach

    OCaml Approach

    OCaml implements the sieve with Array.make (n+1) true and two nested loops using for and while. The functional equivalent uses Array.iteri for the outer loop, though mutable array mutation is idiomatic here. OCaml's Array.to_seqi |> Seq.filter_map (fun (i, b) -> if b then Some i else None) generates prime sequences lazily. The Bigarray module provides compact bit arrays for memory efficiency. OCaml's Printf.printf easily prints prime counts for verification.

    Full Source

    #![allow(clippy::all)]
    //! # Sieve of Eratosthenes
    pub fn sieve(n: usize) -> Vec<bool> {
        let mut is_prime = vec![true; n + 1];
        is_prime[0] = false;
        if n >= 1 {
            is_prime[1] = false;
        }
        for i in 2..=((n as f64).sqrt() as usize) {
            if is_prime[i] {
                for j in (i * i..=n).step_by(i) {
                    is_prime[j] = false;
                }
            }
        }
        is_prime
    }
    
    pub fn primes_up_to(n: usize) -> Vec<usize> {
        sieve(n)
            .iter()
            .enumerate()
            .filter_map(|(i, &b)| if b { Some(i) } else { None })
            .collect()
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
        #[test]
        fn test_sieve() {
            assert_eq!(primes_up_to(20), vec![2, 3, 5, 7, 11, 13, 17, 19]);
        }
    }
    ✓ Tests Rust test suite
    #[cfg(test)]
    mod tests {
        use super::*;
        #[test]
        fn test_sieve() {
            assert_eq!(primes_up_to(20), vec![2, 3, 5, 7, 11, 13, 17, 19]);
        }
    }

    Deep Comparison

    OCaml vs Rust: Sieve Of Eratosthenes

    Overview

    See the example.rs and example.ml files for detailed implementations.

    Key Differences

    AspectOCamlRust
    Type systemHindley-MilnerOwnership + traits
    MemoryGCZero-cost abstractions
    MutabilityExplicit refmut keyword
    Error handlingOption/ResultResult<T, E>

    See README.md for detailed comparison.

    Exercises

  • Implement a segmented sieve that generates primes in the range [L, R] using only O(sqrt(R)) memory.
  • Build the smallest prime factor table: spf[i] = smallest prime dividing i, useful for fast factorization.
  • Compute Euler's totient function for all n ≤ N using a sieve-like approach.
  • Count twin primes (pairs p, p+2 both prime) up to 10^7 and compare with pi(n) estimates.
  • Implement a BitVec-backed sieve and measure memory and speed improvement vs Vec<bool>.
  • Open Source Repos