Случайные целые числа с фиксированной суммой

#javascript

#javascript

Вопрос:

Я пытаюсь создать функцию, которая возвращает массив случайных целых чисел, сумма которых фиксирована.

Вот мой код:

 function arraySum(a) {
  return a.reduce((a, b) => a   b, 0)
}

function getRandomIntInclusive(min, max) {
  const minCeil = Math.ceil(min)
  const maxFloor = Math.floor(max)
  return Math.floor(Math.random() * (maxFloor - minCeil   1))   minCeil
}

function randomNumbersWithFixedSum(quantity, sum) {
  const randoms = [...Array(quantity - 1).keys()].map(q => getRandomIntInclusive(0, sum/quantity))
  const last = sum - arraySum(randoms)
  return [...randoms, last]
}

console.log(randomNumbersWithFixedSum(1, 100))
console.log(randomNumbersWithFixedSum(2, 100))
console.log(randomNumbersWithFixedSum(3, 100))
console.log(randomNumbersWithFixedSum(4, 100))
console.log(randomNumbersWithFixedSum(5, 100)) 

Это работает, но это не совсем то, что я хочу. Я бы хотел, чтобы каждое число было случайным в диапазоне [0, sum] .
В randomNumbersWithFixedSum функции я принудительно ввел первые (quantity-1) числа [0, sum/quantity] , но мне это не нравится.

Как я могу создать действительно случайные числа [0, sum] , сумма которых равна sum ?

Комментарии:

1. Если числа находятся в диапазоне [0,sum] , вы получите много нулей. Это нормально?

2. Нет, я полагаю, у меня много 0, только если sum=0

3. Тогда невозможно иметь N случайных чисел из [0, sum], которые суммируются до суммы. Они не могут быть случайными. Что-то должно дать. Какое приложение для этого?

4. Например, рассмотрим 5 случайных чисел от 0 до 10. Первое число равно 7. Следующее число должно быть от 0 до 3, а не от 0 до 10.

5. Конечно! Именно по этой причине я задаю этот вопрос. Я нашел решение (протестируйте мой код), но я спрашиваю, есть ли лучший и более правильный способ сделать это. Если рассмотреть число 5 в [0 10], а первое равно 10, то результат должен быть [10, 0, 0, 0, 0] , я согласен с вами, но это предельный случай, потому что первое число должно быть 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 или 10

Ответ №1:

Это яркий пример, когда рекурсивная функция может упростить проблему. Каждое выполнение вычисляет только одно случайное число, а затем вызывает себя с обновленными аргументами. Описание см. В комментариях.

 function getRandomNumberBetweenIncluding(min, max) {
    return Math.floor(Math.random() * (max - min   1))   min;
}

function randomNumbersWithFixedSum(quantity, sum) {
    // only a single number required; return the passed sum.
    if (quantity === 1) {
        return [sum];
    }

    // Create one random number and return an array containing that number
    // as first item. Then use the spread operator and recursively execute
    // the function again with a decremented quantity and the updated  
    // maximum possible sum.
    const randomNum = getRandomNumberBetweenIncluding(0, sum);
    return [
        randomNum,
        ...randomNumbersWithFixedSum(quantity - 1, sum - randomNum),
    ];
}

console.log(randomNumbersWithFixedSum(1, 100));
console.log(randomNumbersWithFixedSum(2, 100));
console.log(randomNumbersWithFixedSum(3, 100));
console.log(randomNumbersWithFixedSum(4, 100));
console.log(randomNumbersWithFixedSum(5, 100)); 

Ответ №2:

Как насчет следующего решения:

  1. На первой итерации мы пытаемся получить случайное число между 0 и max — скажем, мы извлекаем N .
  2. На второй итерации — максимально возможное значение не может быть больше max N (в противном случае сумма будет больше max ).
  3. Продолжайте quantity - 1 шаги.
  4. На последнем шаге мы должны использовать то, что осталось до max
 function getRandomIntInclusive(min, max) {
  const minCeil = Math.ceil(min)
  const maxFloor = Math.floor(max)
  return Math.floor(Math.random() * (maxFloor - minCeil   1))   minCeil
}

function randomNumbersWithFixedSum(quantity, sum) {
  const result = [];
  let total = 0;
  
  for (let i = 0; i < quantity - 1; i  ) {
    let max = sum - total;
    let num = getRandomIntInclusive(0, max);
    result.push(num);
    total  = num;
  }
  result.push(sum - total);
  
  return resu<
}

console.log(randomNumbersWithFixedSum(1, 100))
console.log(randomNumbersWithFixedSum(2, 100))
console.log(randomNumbersWithFixedSum(3, 100))
console.log(randomNumbersWithFixedSum(4, 100))
console.log(randomNumbersWithFixedSum(5, 100)) 

Ответ №3:

Вы могли бы взять только остальную часть суммы для максимального случайного числа и перетасовать массив позже, чтобы опустить большие значения только в верхней части массива.

 function shuffle(array) {
    let i = array.length;
    while (--i) {
        let j = Math.floor(Math.random() * (i   1)),
            temp = array[j];
        array[j] = array[i];
        array[i] = temp;
    }
    return array;
}

function getRandomIntInclusive(min, max) {
    const minCeil = Math.ceil(min)
    const maxFloor = Math.floor(max)
    return Math.floor(Math.random() * (maxFloor - minCeil   1))   minCeil
}

function randomNumbersWithFixedSum(length, sum) {
    length--;
    const randoms = Array.from({ length }, q => {
        const r = getRandomIntInclusive(0, sum)
        sum -= r;
        return r;
    });
    return shuffle([...randoms, sum]);
}

console.log(randomNumbersWithFixedSum(5, 100))
console.log(randomNumbersWithFixedSum(4, 100))
console.log(randomNumbersWithFixedSum(3, 100))
console.log(randomNumbersWithFixedSum(2, 100))
console.log(randomNumbersWithFixedSum(1, 100)) 
 .as-console-wrapper { max-height: 100% !important; top: 0; }