Существует ли эффективный способ генерирования случайной комбинации из N целых чисел, такой что
- каждое целое число находится в интервале [
min
,max
], - целые числа имеют сумму
sum
, - целые числа могут появляться в любом порядке (например, в случайном порядке), и
- комбинация выбирается случайным образом из всех комбинаций, которые отвечают другим требованиям?
Существует ли подобный алгоритм для случайных комбинаций, в котором целые числа должны появляться в отсортированном порядке по их значениям (а не в любом порядке)?
(Выбор подходящей комбинации со средним значением mean
является особым случаем, если sum = N * mean
. Эта проблема эквивалентна генерации равномерного случайного разбиения sum
на N частей, каждая из которых находится в интервале [ min
, max
] и появляется в любом порядке или в отсортированном порядке по их значения, в зависимости от обстоятельств.)
Мне известно, что эта проблема может быть решена следующим образом для комбинаций, которые появляются в случайном порядке (EDIT [27 апреля]: алгоритм изменен.):
Если
N * max < sum
илиN * min > sum
, нет решения.Если
N * max == sum
есть только одно решение, в котором всеN
числа равныmax
. ЕслиN * min == sum
есть только одно решение, в котором всеN
числа равныmin
.Используйте алгоритм, приведенный в работах Смита и Тромбла («Выборка из простого симплекса», 2004), чтобы сгенерировать N случайных неотрицательных целых чисел с суммой
sum - N * min
.Добавьте
min
к каждому числу, созданному таким образом.Если любое число больше чем
max
, перейдите к шагу 3.
Тем не менее, этот алгоритм медленный, если max
намного меньше, чем sum
. Например, согласно моим тестам (с реализацией специального случая, описанного выше mean
), алгоритм в среднем отклоняет:
- около 1,6 образцов, если
N = 7, min = 3, max = 10, sum = 42
, но - около 30,6 образцов, если
N = 20, min = 3, max = 10, sum = 120
.
Есть ли способ изменить этот алгоритм, чтобы он был эффективен для больших N, при этом все еще удовлетворяя вышеуказанным требованиям?
РЕДАКТИРОВАТЬ:
В качестве альтернативы, предложенной в комментариях, эффективный способ создания допустимой случайной комбинации (которая удовлетворяет всем требованиям, кроме последнего):
- Рассчитать
X
количество допустимых комбинаций возможно приsum
,min
иmax
. - Выберите
Y
, равномерное случайное число в[0, X)
. - Конвертировать («unrank»)
Y
в правильную комбинацию.
Однако существует ли формула для расчета количества допустимых комбинаций (или перестановок), и есть ли способ преобразовать целое число в действительную комбинацию? [РЕДАКТИРОВАТЬ (28 апреля): то же самое для перестановок, а не комбинаций].
РЕДАКТИРОВАТЬ (27 апреля):
После прочтения « Неравномерного генерирования случайных переменных» Девройа (1986) я могу подтвердить, что это проблема генерации случайного разбиения. Кроме того, упражнение 2 (особенно часть E) на стр. 661 относится к этому вопросу.
РЕДАКТИРОВАТЬ (28 апреля):
Как оказалось, алгоритм, который я дал, является унифицированным, где задействованные целые числа заданы в случайном порядке , а не в отсортированном порядке по их значениям . Поскольку обе проблемы представляют общий интерес, я изменил этот вопрос, чтобы найти канонический ответ для обеих проблем.
Следующий код Ruby может быть использован для проверки потенциальных решений для однородности (где algorithm(...)
алгоритм-кандидат):
combos={}
permus={}
mn=0
mx=6
sum=12
for x in mn..mx
for y in mn..mx
for z in mn..mx
if x+y+z==sum
permus[[x,y,z]]=0
end
if x+y+z==sum and x<=y and y<=z
combos[[x,y,z]]=0
end
end
end
end
3000.times {|x|
f=algorithm(3,sum,mn,mx)
combos[f.sort]+=1
permus[f]+=1
}
p combos
p permus
РЕДАКТИРОВАТЬ (29 апреля): повторно добавлен код Ruby текущей реализации.
Следующий пример кода приведен в Ruby, но мой вопрос не зависит от языка программирования:
def posintwithsum(n, total)
raise if n <= 0 or total <=0
ls = [0]
ret = []
while ls.length < n
c = 1+rand(total-1)
found = false
for j in 1...ls.length
if ls[j] == c
found = true
break
end
end
if found == false;ls.push(c);end
end
ls.sort!
ls.push(total)
for i in 1...ls.length
ret.push(ls[i] - ls[i - 1])
end
return ret
end
def integersWithSum(n, total)
raise if n <= 0 or total <=0
ret = posintwithsum(n, total + n)
for i in 0...ret.length
ret[i] = ret[i] - 1
end
return ret
end
# Generate 100 valid samples
mn=3
mx=10
sum=42
n=7
100.times {
while true
pp=integersWithSum(n,sum-n*mn).map{|x| x+mn }
if !pp.find{|x| x>mx }
p pp; break # Output the sample and break
end
end
}
источник
sum
иN
фактически неограниченны (в пределах разумного). Я ищу канонический ответ, потому что основная проблема всплывает во многих вопросах, задаваемых по переполнению стека, включая этот и этот . @ גלעדברקןОтветы:
Вот мое решение на Java. Он полностью функционален и содержит два генератора:
PermutationPartitionGenerator
для несортированных разделов иCombinationPartitionGenerator
для отсортированных разделов. Ваш генератор также реализован в классеSmithTromblePartitionGenerator
для сравнения. КлассSequentialEnumerator
перечисляет все возможные разделы (не отсортированные или отсортированные в зависимости от параметра) в последовательном порядке. Я добавил тщательные тесты (включая ваши тестовые случаи) для всех этих генераторов. Реализация по большей части самоочевидна. Если у вас есть вопросы, я отвечу на них через пару дней.Вы можете попробовать это на Ideone .
источник
Вот алгоритм от PermutationPartitionGenerator Джона Макклэйна, в другом ответе на этой странице. Он имеет две фазы, а именно фазу настройки и фазу выборки, и генерирует
n
случайные числа в [min
,max
] с суммойsum
, где числа перечислены в случайном порядке.Этап настройки: сначала строится таблица решений с использованием следующих формул (
t(y, x)
гдеy
находится в [0,n
] иx
в [0,sum - n * min
]):Здесь t (y, x) хранит относительную вероятность того, что сумма
y
чисел (в соответствующем диапазоне) будет равнаx
. Эта вероятность относительно всех t (y, x) с одинаковымиy
.Фаза выборки: здесь мы генерируем выборку
n
чисел. Установитеs
дляsum - n * min
, затем для каждой позицииi
, начиная сn - 1
и возвращаясь к 0:v
случайное целое число в [0, t (i + 1, s)).r
вmin
.v
.v
остается 0 или больше, вычтите t (i, s-1) изv
, добавьте 1 кr
и вычтите 1 изs
.i
в образце установлено наr
.РЕДАКТИРОВАТЬ:
Похоже, что при незначительных изменениях в вышеприведенном алгоритме возможно, чтобы каждое случайное число использовало отдельный диапазон, а не использовал один и тот же диапазон для всех них:
Каждое случайное число в позициях
i
∈ [0,n
) имеет минимальное значение min (i) и максимальное значение max (i).Пусть
adjsum
=sum
- Σmin (i).Этап настройки: сначала строится таблица решений с использованием следующих формул (
t(y, x)
гдеy
находится в [0,n
] иx
в [0,adjsum
]):Фазы дискретизации затем точно так же , как и раньше, за исключением того, мы установили ,
s
чтобыadjsum
(а неsum - n * min
) и множествоr
в мин (я) (а неmin
).РЕДАКТИРОВАТЬ:
Для CombinationPartitionGenerator Джона Макклейна фазы настройки и выборки следующие.
Этап настройки: сначала строится таблица решений с использованием следующих формул (
t(z, y, x)
гдеz
находится в [0,n
],y
в [0,max - min
] иx
в [0,sum - n * min
]):Фаза выборки: здесь мы генерируем выборку
n
чисел. Установитеs
вsum - n * min
иmrange
кmax - min
, то для каждой позицииi
, начиная сn - 1
и работает в обратном направлении до 0:v
случайное целое число в [0, t (i + 1, mrange, s)).mrange
на мин (mrange
,s
)mrange
изs
.r
вmin + mrange
.i
,mrange
,s
) изv
.v
остается 0 или больше, добавьте 1 кs
, вычесть 1 изr
и 1 изmrange
, а затем вычтите т (i
,mrange
,s
) изv
.i
в образце установлено наr
.источник
Я не проверял это, так что на самом деле это не ответ, а просто попытка, которая слишком длинна, чтобы вписаться в комментарий. Начните с массива, который соответствует первым двум критериям, и поиграйте с ним, чтобы он по-прежнему соответствовал первым двум, но гораздо более случайный.
Если среднее значение является целым числом, то ваш начальный массив может быть [4, 4, 4, ... 4] или может быть [3, 4, 5, 3, 4, 5, ... 5, 8, 0] или что-то простое, как это. Для среднего значения 4,5 попробуйте [4, 5, 4, 5, ... 4, 5].
Далее выберите пару чисел
num1
иnum2
в массиве. Вероятно, первое число должно быть взято по порядку, как и в случае тасования Фишера-Йейтса, второе число должно выбираться случайным образом. Принятие первого номера по порядку гарантирует, что каждый номер будет выбран хотя бы один раз.Теперь посчитаем
max-num1
иnum2-min
. Это расстояние от двух чисел кmax
иmin
границам. Установитеlimit
меньшее из двух расстояний. Это максимально допустимое изменение, которое не поставит одно или другое число за допустимые пределы. Еслиlimit
ноль, то пропустите эту пару.Выберите случайное целое число в диапазоне [1,
limit
]: вызовите егоchange
. Я опускаю 0 из диапазона выбора, поскольку это не имеет никакого эффекта. Тестирование может показать, что вы получаете лучшую случайность, включая ее; Я не уверен.Теперь установите
num1 <- num1 + change
иnum2 <- num2 - change
. Это не повлияет на среднее значение, и все элементы массива все еще находятся в требуемых границах.Вам нужно будет пройти через весь массив хотя бы один раз. Тестирование должно показать, нужно ли вам проходить через него несколько раз, чтобы получить что-то достаточно случайное.
ETA: включить псевдокод
источник
Как указывает ОП, способность эффективно отменить ставку очень мощная. Если мы сможем это сделать, генерация равномерного распределения разделов может быть выполнена в три этапа (повторяя то, что ОП изложил в вопросе):
sum
, чтобы части находились в диапазоне [min
,max
].[1, M]
.Ниже мы сконцентрируемся только на генерации n- го раздела, поскольку существует огромное количество информации о генерации равномерного распределения целых чисел в заданном диапазоне. Вот простой
C++
алгоритм отмены рейтинга, который должен быть легко переведен на другие языки (NB. Я еще не выяснил, как отменить выбор композиции (т.е. порядок имеет значение)).pCount
Функцию рабочей лошадки дают:Эта функция основана на превосходном ответе на вопрос: существует ли эффективный алгоритм целочисленного разбиения с ограниченным числом частей? пользователем @ m69_snarky_and_unwelcoming. Тот, что приведен выше, представляет собой небольшую модификацию простого алгоритма (без напоминания). Это может быть легко изменено, чтобы включить памятку для большей эффективности. Мы пока оставим это без внимания и сосредоточимся на части с нефиксированными значениями.
Объяснение
unRank
Прежде всего отметим, что существует взаимно-однозначное сопоставление разделов длины N числа,
sum
таких, что части находятся в диапазоне [min
,max
], с ограниченными разделами длины N числаsum - N * (min - 1)
с частями в [1
,max - (min - 1)
].В качестве небольшого примера рассмотрим разбиения
50
длины4
такие, чтоmin = 10
иmax = 15
. Это будет иметь ту же структуру, что и ограниченные разделы50 - 4 * (10 - 1) = 14
длины4
с максимальной частью, равной15 - (10 - 1) = 6
.Имея это в виду, чтобы можно было легко сосчитать, мы могли бы добавить шаг 1a, чтобы перевести проблему в «единичный» случай, если хотите.
Теперь у нас просто есть проблема со счетом. Как блестяще показывает @ m69, подсчет разделов может быть легко достигнут, если разбить проблему на более мелкие задачи. Функция @ m69 дает нам 90% пути, мы просто должны выяснить, что делать с добавленным ограничением, что есть ограничение. Вот где мы получаем:
Мы также должны помнить, что это
myMax
будет уменьшаться по мере нашего продвижения вперед. Это имеет смысл, если мы посмотрим на 6- й раздел выше:Для того чтобы отсчитать количество разделов, мы должны продолжать применять перевод к «единичному» случаю. Это выглядит так:
Если в качестве шага раньше у нас был максимум
6
, то теперь мы рассматриваем только максимум5
.Имея это в виду, отмена выбора раздела ничем не отличается от отмены выбора стандартной перестановки или комбинации. Мы должны быть в состоянии посчитать количество разделов в данном разделе. Например, чтобы подсчитать количество разделов, которые начинаются с
10
выше, все, что мы делаем, это удаляем10
в первом столбце:Перевести на корпус устройства:
и позвоните
pCount
:Учитывая случайное целое число, которое нельзя отменить, мы продолжаем вычислять количество секций в меньших и меньших секциях (как мы делали выше), пока не заполним наш индексный вектор.
Примеры
Учитывая
min = 3
,max = 10
,n = 7
иsum = 42
, вот ideone демо , которая генерирует 20 случайных разделов. Выход ниже:Слева находится лексикографический указатель, а справа - раздел без рейтинга.
источник
Если вы сгенерируете 0≤a≤1 случайных значений в диапазоне [l, x-1] равномерно и 1-a случайных значений в диапазоне [x, h] равномерно, ожидаемое среднее значение будет:
Так что если вы хотите конкретный m, вы можете играть с а и х.
Например, если вы установите x = m: a = (hm) / (h-l + 1).
Чтобы обеспечить близкую к равномерной вероятность для различных комбинаций, выберите a или x случайным образом из набора допустимых решений уравнения выше. (x должно быть в диапазоне [l, h] и должно быть (близко к) целому числу; N * a также должно быть (близко к) целому числу.
источник