How to split a number N into M random positive parts whose sum is exactly N in Scala

1 Answer

0 votes
import scala.util.Random
import scala.collection.mutable

/*
    Split a number N into M random positive parts that sum to N.
 
    Algorithm (uniform integer composition):
    ----------------------------------------
    1. We want M positive integers p1, p2, ..., pM such that:
           p1 + p2 + ... + pM = N
 
    2. Generate (M - 1) random "cut points" in the range [1, N - 1].
       Example: N = 20, M = 4 → generate 3 cuts, e.g. {3, 11, 17}
       
    3. Sort the cut points.
 
    4. The parts are the differences between consecutive cuts:
         p1 = cut(0)
         p2 = cut(1) - cut(0)
         ...
         pM = N - cut(M-2)
 
    This produces a uniformly random composition of N into M parts.
*/

/*
    Important:
    -----------
    Random cut points must be UNIQUE.
    If two cut points are equal, their difference becomes 0,
    which produces an invalid part.
*/

// Generate M random positive parts summing to N
def splitIntoRandomParts(N: Int, M: Int): Vector[Int] = {
  if (M <= 0 || N < M)
    throw new IllegalArgumentException("Invalid N or M: require N >= M >= 1")

  val cuts = mutable.ArrayBuffer[Int]()
  val used = mutable.Set[Int]()   // ensures uniqueness

  val rng = new Random()

  // Generate UNIQUE cut points
  while (cuts.size < M - 1) {
    val c = 1 + rng.nextInt(N - 1)
    if (!used.contains(c)) {
      used += c
      cuts += c
    }
  }

  // Sort the cut points
  val sortedCuts = cuts.sorted

  // Build the parts
  val parts = mutable.ArrayBuffer[Int]()
  var prev = 0

  for (c <- sortedCuts) {
    parts += (c - prev)   // guaranteed >= 1
    prev = c
  }

  parts += (N - prev)     // last part, also >= 1

  parts.toVector
}

@main def main(): Unit = {
  val N = 30
  val M = 5

  val parts = splitIntoRandomParts(N, M)

  println(s"Splitting N = $N into M = $M random parts:")
  println(parts.mkString(" "))
}


/*
run:

Splitting N = 30 into M = 5 random parts:
10 5 4 8 3

*/

 



answered Jul 19 by avibootz

Related questions

...