๐Ÿฆ€ Functional Rust

024: Lotto Draw

Difficulty: 1 Level: Beginner Draw N distinct numbers from the range 1..M โ€” the classic lottery algorithm.

The Problem This Solves

The Dutch Staatsloterij, Powerball, and every other lottery works the same way: pick 6 distinct numbers from 1 to 49. This is so common it deserves its own clean abstraction separate from the general "random select" pattern. The difference from example 023 is that here we're always sampling from a range (integers 1 to M), not an arbitrary list. That makes the intent crystal clear: `lotto_select(6, 49, seed)` reads exactly like the problem statement. In Python you'd write `random.sample(range(1, 50), 6)`. This example shows the Rust equivalent built from first principles. It also demonstrates assertion-based contracts: if you ask for more numbers than the range contains, we `assert!` immediately rather than silently returning a shorter result. Fail loudly at the boundary condition.

The Intuition

We generate the full range `[1, 2, 3, ..., M]` as a `Vec`, then use the same pool-shrinking trick from example 023: pick a random index, remove it from the pool, add it to results. After N steps, we have N distinct numbers from the range with uniform probability. The key insight is that generating the full range first is clean but costs O(M) memory. For a 6/49 lottery that's fine. For "pick 10 from 1 million" you'd use a different approach (hash set tracking used numbers, or Knuth's algorithm). For learning purposes, the full-range approach is the clearest. Why distinct? Lottery rules require distinct numbers โ€” you can't win with [7, 7, 12, 33, 41, 49]. Removing from the pool after each pick enforces this structurally: it's impossible to select the same number twice.

How It Works in Rust

fn lotto_select(n: usize, m: usize, seed: u64) -> Vec<usize> {
 assert!(n <= m, "cannot draw more numbers than the range");

 let mut rng = Lcg::new(seed);
 // Build the full range โ€” this is our "pool"
 let mut pool: Vec<usize> = (1..=m).collect();
 let mut result = Vec::with_capacity(n);

 for _ in 0..n {
     let idx = rng.next_usize(pool.len());
     result.push(pool.remove(idx));  // remove guarantees no duplicates
 }
 result
}
The `(1..=m).collect()` creates `[1, 2, ..., M]`. Each `pool.remove(idx)` shrinks the pool, so by round K we're picking uniformly from M-K+1 remaining numbers. This is equivalent to a partial Fisher-Yates shuffle. Verification that results are distinct:
let mut sorted = draw.clone();
sorted.sort();
sorted.dedup();
assert_eq!(sorted.len(), draw.len()); // no duplicates

What This Unlocks

Key Differences

ConceptOCamlRust
Range to list`List.init m (fun i -> i + 1)``(1..=m).collect::<Vec<_>>()`
Remove nth elementRecursive pattern match on list`Vec::remove(idx)`
Assertion`assert (n <= m)``assert!(n <= m, "message")`
Distinct guaranteeStructural โ€” list shrinksStructural โ€” Vec shrinks; same idea
// Lotto Draw โ€” 99 Problems #24
// Draw n different random numbers from the range 1..m.
// lotto_select 6 49 โ†’ [some 6 distinct numbers from 1..49]

struct Lcg {
    state: u64,
}

impl Lcg {
    fn new(seed: u64) -> Self {
        Lcg { state: seed }
    }

    fn next_usize(&mut self, modulus: usize) -> usize {
        self.state = self.state.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
        ((self.state >> 33) as usize) % modulus
    }
}

/// Draw n distinct numbers from [1..=m].
fn lotto_select(n: usize, m: usize, seed: u64) -> Vec<usize> {
    assert!(n <= m, "cannot draw more numbers than the range");
    let mut rng = Lcg::new(seed);
    let mut pool: Vec<usize> = (1..=m).collect();
    let mut result = Vec::with_capacity(n);

    for _ in 0..n {
        let idx = rng.next_usize(pool.len());
        result.push(pool.remove(idx));
    }
    result
}

fn main() {
    let draw = lotto_select(6, 49, 42);
    println!("Lotto 6/49 (seed=42):  {:?}", draw);

    let draw2 = lotto_select(6, 49, 7);
    println!("Lotto 6/49 (seed=7):   {:?}", draw2);

    let small = lotto_select(3, 10, 99);
    println!("Draw 3 from 10:        {:?}", small);

    // Verify all distinct
    let mut sorted = draw.clone();
    sorted.sort();
    sorted.dedup();
    assert_eq!(sorted.len(), draw.len());
    println!("All distinct: โœ“");
}

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

    #[test]
    fn test_correct_count() {
        let draw = lotto_select(6, 49, 42);
        assert_eq!(draw.len(), 6);
    }

    #[test]
    fn test_all_in_range() {
        let draw = lotto_select(6, 49, 42);
        assert!(draw.iter().all(|&x| x >= 1 && x <= 49));
    }

    #[test]
    fn test_all_distinct() {
        let draw = lotto_select(6, 49, 42);
        let mut sorted = draw.clone();
        sorted.sort();
        sorted.dedup();
        assert_eq!(sorted.len(), 6);
    }

    #[test]
    fn test_deterministic() {
        assert_eq!(lotto_select(6, 49, 42), lotto_select(6, 49, 42));
    }
}
(* Lotto Draw *)
(* OCaml 99 Problems #24 *)

(* Implementation for example 24 *)

(* Tests *)
let () =
  (* Add tests *)
  print_endline "โœ“ OCaml tests passed"