How to generate random positive integers that sum to N in Rust

1 Answer

0 votes
use rand::rng;

/*
    Generate k random positive integers that sum to n.

    Algorithm (stick-breaking / random composition):
    ------------------------------------------------
    1. We choose (k - 1) random "cut points" in the range [1, n - 1].
       These represent where we "break" the integer n into k parts.

    2. Sort the cut points.

    3. Compute the differences between consecutive points
       (including 0 and n as boundaries). These differences
       are the k positive integers that sum to n.

    This produces a uniformly random composition of n.
*/

/*
    Paper Run (Dry Run) of the Random-Sum Program
    ---------------------------------------------

    Input:
        n = 50
        k = 7

    We need (k - 1) = 6 random cut points in the range [1, 49].

    Simulated rand() outputs (rand() % 49):
        12, 3, 40, 25, 7, 33

    After adding +1 (because the code uses 1 + rand() % (n - 1)):
        cuts = {13, 4, 41, 26, 8, 34}

    Step 1: Sort the cut points
        cuts → {4, 8, 13, 26, 34, 41}

    Step 2: Convert cut points into segment lengths
        prev = 0

        out[0] = 4  - 0  = 4
        prev = 4

        out[1] = 8  - 4  = 4
        prev = 8

        out[2] = 13 - 8  = 5
        prev = 13

        out[3] = 26 - 13 = 13
        prev = 26

        out[4] = 34 - 26 = 8
        prev = 34

        out[5] = 41 - 34 = 7
        prev = 41

        out[6] = 50 - 41 = 9   (final segment)

    Final result:
        parts = {4, 4, 5, 13, 8, 7, 9}

    Verification:
        4 + 4 + 5 + 13 + 8 + 7 + 9 = 50

    Output:

        Random parts that sum to 50:
        4 4 5 13 8 7 9
        Sum = 50
*/
fn generate_random_sum(n: i32, k: i32) -> Vec<i32> {
    if k <= 0 || n < k {
        // k positive integers must sum to n → minimum sum is k
        panic!("Invalid n or k");
    }

    let mut rng = rng(); // thread-local RNG (rand 0.9+)

    // To ensure positive integers, we need (k - 1) DISTINCT cut points
    // in the range [1, n - 1]. We sample indices from 0..(n-1) without replacement.
    let sample = rand::seq::index::sample(&mut rng, (n - 1) as usize, (k - 1) as usize);
    
    // Convert to 1-based indices and collect into a vector
    let mut cuts: Vec<i32> = sample.into_iter().map(|idx| (idx + 1) as i32).collect();

    // Sort the cut points so we can compute segment lengths
    cuts.sort_unstable();

    let mut result: Vec<i32> = Vec::with_capacity(k as usize);
    let mut prev: i32 = 0;

    // Convert cut points into segment lengths
    for c in cuts {
        result.push(c - prev);
        prev = c;
    }

    // Last segment: from last cut to n
    result.push(n - prev);

    result
}

fn main() {
    let n: i32 = 50; // total sum
    let k: i32 = 7;  // number of random parts

    let parts = generate_random_sum(n, k);

    println!("Random parts that sum to {}:", n);
    for x in &parts {
        print!("{} ", x);
    }
    println!();

    // Verify sum
    let sum: i32 = parts.iter().sum();
    println!("Sum = {}", sum);
}


/*
run:

Random parts that sum to 50:
5 5 13 7 5 9 6 
Sum = 50

*/

 



answered 4 days ago by avibootz
...