Преобразовать набор больших целых чисел в набор маленьких

Как перекодировать набор строго возрастающих (или строго убывающих) положительных целых чисел P, чтобы уменьшить количество положительных целых чисел, которые могут встречаться между целыми числами в нашем наборе?

Зачем нам это делать? Допустим, мы хотим случайным образом выбрать P, но 1.) P слишком велико для перечисления, и 2.) элементы P связаны неслучайным образом, но определенным образом. способ, который слишком сложен для выборки. Однако мы узнаем член P, когда видим его. Скажем, мы знаем P[0] и P[n], но не можем принять идею перечисления всех P или точного понимания того, как связаны члены P. Точно так же количество всех возможных целых чисел, встречающихся между P[0] и P[n], во много раз превышает размер P, что делает вероятность случайного рисования члена P очень малой.

Пример. Пусть P[0] = 2101010101 и P[n] = 505050505. Теперь, возможно, нас интересуют только целые числа между P[0] и P[n], имеющие определенное качество ( например, сумма всех целых чисел в P[x] равна Q или меньше, каждый элемент P имеет 7 или меньше как наибольшее целое число). Итак, не все положительные целые числа P[n] ‹= X ‹= P[0] принадлежат P. Интересующий меня P обсуждается в комментариях ниже.

Что я пробовал: если P является строго убывающим множеством и мы знаем P[0] и P[n], то мы можем рассматривать каждый член, как если бы он был вычтен из P[0]. Это уменьшает каждое число, возможно, значительно, и сохраняет каждый элемент как уникальное целое число. Для интересующего меня P (ниже) можно рассматривать каждое уменьшенное значение P как деление на общий знаменатель (9,11,99), что уменьшает количество возможных целых чисел между членами P. Я обнаружили, что при совместном использовании эти подходы уменьшают набор всех P[0] ‹= X ‹= P[n] на несколько порядков, увеличивая вероятность случайного выбора члена P из всех положительных целых чисел P[n ] ‹= X ‹= P[0] все еще очень мало.

Примечание. Как должно быть ясно, мы должны что-то знать о P. Если мы этого не знаем, это в основном означает, что мы понятия не имеем, что ищем. Когда мы случайным образом выбираем целые числа между P[0] и P[n] (перекодированные или нет), мы должны иметь возможность сказать «Ага, это принадлежит P.», если это действительно так.

Хороший ответ мог бы значительно увеличить практическое применение разработанного мной вычислительного алгоритма. Пример того вида P, который меня интересует, приведен в комментарии 2. Я непреклонен в том, чтобы отдать должное.


person Community    schedule 15.02.2013    source источник
comment
У вас есть возможность сопоставить k с P[k]? Откуда вы знаете, что такое P[k]? Отчасти это может зависеть от того, чем на самом деле являются эти P[k] и как они представлены.   -  person mhum    schedule 16.02.2013
comment
Каждый член множества P соответствует целочисленному разделу Q на N частей. например пусть Q = 20 и N = 4, тогда первое лексическое разбиение для Q и N, то есть [17,1,1,1], отображается в P как 17010101 (т.е. P[0]). Следовательно, целочисленному разделу [5,5,5,5] соответствует 5050505. Суть? Произвольная выборка из набора всех целочисленных разделов Q, состоящего из N частей, без рекурсии и с низким уровнем отклонения. Там я все отдал. Это того стоит ради ответа.   -  person klocey    schedule 16.02.2013
comment
Можно также добавить некоторое поощрение: этот метод быстро ослепляет, когда N меньше 5. Под быстрым ослеплением я имею в виду, что он создает событие с вероятностью, слишком малой, чтобы мой компьютер не мог его вычислить (например, используя типичные алгоритмы случайного разбиения) стать очень вероятным.   -  person klocey    schedule 16.02.2013
comment
В порядке. Можем ли мы предположить, что нули не допускаются и порядок не имеет значения? Я думаю, что это обычно для целочисленных разделов, но я не уверен в вашем случае. Если да, то можете ли вы подтвердить, что хотите получить единую выборку разделов ровно из N частей?   -  person mhum    schedule 16.02.2013
comment
Заполнение нулями (т.е. 17,1,1,1 -> 17010101) явно увеличивает размер чисел, значительно. Однако отсутствие заполнения нулями (т.е. 17,1,1,1 -> 17111) не допускает (P[0] › P[1] › ...), и хотя легко перейти от 17,1, от 1,1 до 17111, может быть сложно или несвоевременно рисовать случайное число (например, 12332), а затем знать, что оно декодируется до 12,3,3,2. ... особенно, если, скажем, Q = 50000 и N = 953. Короче говоря, весь процесс состоит из 1.) случайного выбора числа в диапазоне от P[0] до P[n] 2.) декодирования его и проверки результирующая последовательность принадлежит P(Q,N), т. е. множеству разбиений Q на N частей.   -  person klocey    schedule 16.02.2013
comment
Извините, я не ясно выразился. Можем ли мы предположить, что [20,0,0,0] не является допустимым разделом 20? И можем ли мы предположить, что [17,1,1,1] это то же самое, что и [1,1,17,1]?   -  person mhum    schedule 16.02.2013
comment
Таким образом, [20,0,0,0] не будет допустимым разделом. Кроме того, в этом случае важен порядок, потому что каждый раздел должен иметь одинаковую вероятность быть отрисованным, а некоторые лексически упорядоченные разделы Q и N имеют разное количество неупорядоченных последовательностей (назовем их микросостояниями), из-за чего некоторые целочисленные разделы более вероятно, чем другие.   -  person klocey    schedule 16.02.2013
comment
Рассматривая порядок как отдельный, вы хотите сэмплировать раздел, содержащий элементы {1,2,3,14}, в 24 раза чаще, чем раздел, содержащий элементы {5,5,5,5}, верно? Здесь вас подвели стандартные алгоритмы выборки разделов?   -  person mhum    schedule 16.02.2013
comment
Стандартные алгоритмы разбиения не выполняют выборку по отношению к N и, следовательно, требуют высокой степени отклонения. Я разработал алгоритм, который делает выборку в соответствии с Q&N (goo.gl/7C3L2), но, как и другие алгоритмы, он опирается на на рекурсии. Рекурсия является проблемой, потому что 1.) она может занять много времени и 2.) она может легко максимизировать пределы рекурсии и размер стека для разумных Q и N. Решение, которое мы обсуждаем, является нерекурсивным, но задерживается из-за множества неиспользуемые числа между P[0] и P[n].   -  person klocey    schedule 16.02.2013
comment
Чтобы было понятнее ваш комментарий и не говоря о перекодировании целых чисел, раздел [14,3,2,1] может быть представлен только числом 14030201 или 14321 или чем-то подобным уникальным. Причина в том, что [14,3,2,1] не может иметь более высокую вероятность выпадения, чем [5,5,5,5] или [17,1,1,1].   -  person klocey    schedule 16.02.2013
comment
В порядке. Понял. Я так понимаю, приведенное выше связанное решение слишком медленно для вас и/или разбивает стек? Я не ожидал этого, но я поверю вам на слово. Я ожидал, что глубина рекурсии будет не больше N, но, похоже, нет.   -  person mhum    schedule 16.02.2013
comment
Итак, связанное решение представляет собой элегантный алгоритм, но его реализация использует рекурсию. Мне пришлось бы установить довольно высокий предел рекурсии для Q = 3000 и N = 200. Даже если бы это сработало, это заняло бы очень много времени. Я нашел другие способы обойти рекурсию, используя очень простые подходы (например, goo.gl/t9D5s), но тот, который мы обсуждаем, представляет собой сложную задачу.   -  person klocey    schedule 16.02.2013
comment
Если вы просите дать эффективный алгоритм равномерной выборки целочисленных разделов фиксированной длины, то известного решения не существует.   -  person PengOne    schedule 16.02.2013
comment
@PengOne Я разработал 2 алгоритма для создания однородных случайных целочисленных разделов с учетом общего Q и количества частей N. Один здесь: dx.doi.org/10.6084/m9.figshare.156290. Другой — тот, что в этой строке комментариев. Концепция каждого из них проста, и каждый из них быстр по-своему. Обсуждаемый здесь подход не полагается на рекурсию и быстр для комбинаций Q и N, с которыми нельзя справиться с помощью других алгоритмов. Но на самом деле вопрос касается перекодирования целых чисел, а не случайного разбиения целых чисел. Тем не менее, спасибо.   -  person klocey    schedule 16.02.2013
comment
Ну, мы должны знать кое-что о P. Очевидно, что вы не можете сделать это для произвольного P, так как нет способа отклонить любое целое число, не вычислив сначала P для него.   -  person Keith Randall    schedule 16.02.2013
comment
Привет Кит. Я отсылаю вас ко второму комментарию в строке. Это явно. Однако ответ не обязательно должен относиться к моему P. Не стесняйтесь выбирать P, если он соответствует условиям в теле вопроса.   -  person klocey    schedule 16.02.2013
comment
Можете ли вы объяснить, почему вы не можете просто сохранить целочисленный раздел в массиве или контейнере какого-либо вида, а затем просто использовать генератор псевдослучайных чисел для случайного выбора? Я не понимаю, как вы отбираете данные и почему есть отклонения. Можете ли вы отредактировать свой вопрос и показать пример того, как вы хотели бы выбрать данные?   -  person Bob Bryan    schedule 16.02.2013
comment
Я хочу, чтобы этот вопрос не путали с вопросом о целочисленном разбиении. Я использовал целочисленное разбиение в качестве примера того, как мы можем знать что-то о P, но при этом иметь следующее: 1. P — набор целых чисел, разница между целыми числами составляет >1 (например, P[0] = 480101, P[1] = 470201) 2.) P слишком велико, чтобы его можно было удержать, и его изучение занимает слишком много времени. 3.) Мы не знаем точного отношения между членами P, т. е. мы не можем сказать, что такое 10^9 член P, даже если P подчиняется правилам (комментарий 2). Но мы можем рассматривать P как перекодированный таким образом, чтобы уменьшить различия между элементами.   -  person klocey    schedule 16.02.2013
comment
@BobBryan существует процент отказов, потому что P намного меньше, чем набор всех положительных целых чисел, которые равны ‹= P[0] и ›= P[n], и мы вынуждены случайным образом извлекать из этого большего набора, чтобы получить однородный случайный Выборка P. Обработка P как перекодируемого может увеличить вероятность случайного рисования члена P (см. основную часть вопроса), снизив частоту отказа, т. е. частоту, когда мы рисуем число и говорим: «Дерьмо, это не принадлежит». верхняя.'   -  person klocey    schedule 16.02.2013


Ответы (2)


Хотя исходный вопрос касается очень общего сценария, касающегося целочисленных кодировок, я бы предположил, что маловероятно, что существует подход, который работает в полной общности. Например, если P[i] более или менее случайны (с точки зрения теории информации), я был бы удивлен, если бы что-то работало.

Итак, вместо этого давайте обратим наше внимание на актуальную проблему OP создания разделов целого числа N, содержащих ровно K частей. При кодировании комбинаторных объектов как целых чисел нам надлежит сохранить как можно больше комбинаторной структуры. Для этого обратимся к классическому тексту Комбинаторные алгоритмы Найенхейса и Уилфа, в частности, главу 13. Фактически, в этой главе они демонстрируют структуру для перечисления и выборки из ряда комбинаторных семейств, включая разбиения N, где наибольшая часть равна K. Использование известная двойственность между разделами с K частями и разделами, где наибольшая часть равна K (возьмем транспонирование Диаграмма Феррерса), мы обнаруживаем, что нам нужно только внести изменения в процесс декодирования.

В любом случае, вот исходный код:

import sys
import random
import time

if len(sys.argv) < 4 :
    sys.stderr.write("Usage: {0} N K iter\n".format(sys.argv[0]))
    sys.stderr.write("\tN = number to be partitioned\n")
    sys.stderr.write("\tK = number of parts\n")
    sys.stderr.write("\titer = number of iterations (if iter=0, enumerate all partitions)\n")
    quit()

N = int(sys.argv[1])
K = int(sys.argv[2])
iters = int(sys.argv[3])

if (N < K) :
    sys.stderr.write("Error: N<K ({0}<{1})\n".format(N,K))
    quit()

# B[n][k] = number of partitions of n with largest part equal to k
B = [[0 for j in range(K+1)] for i in range(N+1)] 

def calc_B(n,k) :
    for j in xrange(1,k+1) :
        for m in xrange(j, n+1) :
            if j == 1 :
                B[m][j] = 1
            elif m - j > 0 :
                B[m][j] = B[m-1][j-1] + B[m-j][j]
            else :
                B[m][j] = B[m-1][j-1]

def generate(n,k,r=None) :
    path = []
    append = path.append

    # Invalid input
    if n < k or n == 0 or k == 0: 
        return []

    # Pick random number between 1 and B[n][k] if r is not specified
    if r == None :
        r = random.randrange(1,B[n][k]+1)

    # Construct path from r    
    while r > 0 :
        if n==1 and k== 1:
            append('N')
            r = 0   ### Finish loop
        elif r <= B[n-k][k] and B[n-k][k] > 0  : # East/West Move
            append('E')
            n = n-k
        else : #  Northeast/Southwest move
            append('N')
            r -= B[n-k][k]
            n = n-1
            k = k-1

    # Decode path into partition    
    partition = []
    l = 0
    d = 0    
    append = partition.append    
    for i in reversed(path) :
        if i == 'N' :
            if d > 0 : # apply East moves all at once
                for j in xrange(l) :
                    partition[j] += d
            d = 0  # reset East moves
            append(1) # apply North move
            l += 1            
        else :
            d += 1 # accumulate East moves    
    if d > 0 : # apply any remaining East moves
        for j in xrange(l) :
            partition[j] += d

    return partition


t = time.clock()
sys.stderr.write("Generating B table... ")    
calc_B(N, K)
sys.stderr.write("Done ({0} seconds)\n".format(time.clock()-t))

bmax = B[N][K]
Bits = 0
sys.stderr.write("B[{0}][{1}]: {2}\t".format(N,K,bmax))
while bmax > 1 :
    bmax //= 2
    Bits += 1
sys.stderr.write("Bits: {0}\n".format(Bits))

if iters == 0 : # enumerate all partitions
    for i in xrange(1,B[N][K]+1) :
        print i,"\t",generate(N,K,i)

else : # generate random partitions
    t=time.clock()
    for i in xrange(1,iters+1) :
        Q = generate(N,K)
        print Q
        if i%1000==0 :
            sys.stderr.write("{0} written ({1:.3f} seconds)\r".format(i,time.clock()-t))

    sys.stderr.write("{0} written ({1:.3f} seconds total) ({2:.3f} iterations per second)\n".format(i, time.clock()-t, float(i)/(time.clock()-t) if time.clock()-t else 0))

А вот несколько примеров производительности (на MacBook Pro 8.3, 2 ГГц i7, 4 ГБ, Mac OSX 10.6.3, Python 2.6.1):

mhum$ python part.py 20 5 10
Generating B table... Done (6.7e-05 seconds)
B[20][5]: 84    Bits: 6
[7, 6, 5, 1, 1]
[6, 6, 5, 2, 1]
[5, 5, 4, 3, 3]
[7, 4, 3, 3, 3]
[7, 5, 5, 2, 1]
[8, 6, 4, 1, 1]
[5, 4, 4, 4, 3]
[6, 5, 4, 3, 2]
[8, 6, 4, 1, 1]
[10, 4, 2, 2, 2]
10 written (0.000 seconds total) (37174.721 iterations per second)

mhum$ python part.py 20 5 1000000 > /dev/null
Generating B table... Done (5.9e-05 seconds)
B[20][5]: 84    Bits: 6
100000 written (2.013 seconds total) (49665.478 iterations per second)

mhum$ python part.py 200 25 100000 > /dev/null
Generating B table... Done (0.002296 seconds)
B[200][25]: 147151784574    Bits: 37
100000 written (8.342 seconds total) (11987.843 iterations per second)

mhum$ python part.py 3000 200 100000 > /dev/null
Generating B table... Done (0.313318 seconds)
B[3000][200]: 3297770929953648704695235165404132029244952980206369173   Bits: 181
100000 written (59.448 seconds total) (1682.135 iterations per second)

mhum$ python part.py 5000 2000 100000 > /dev/null
Generating B table... Done (4.829086 seconds)
B[5000][2000]: 496025142797537184410324290349759736884515893324969819660    Bits: 188
100000 written (255.328 seconds total) (391.653 iterations per second)

mhum$ python part-final2.py 20 3 0
Generating B table... Done (0.0 seconds)
B[20][3]: 33    Bits: 5
1   [7, 7, 6]
2   [8, 6, 6]
3   [8, 7, 5]
4   [9, 6, 5]
5   [10, 5, 5]
6   [8, 8, 4]
7   [9, 7, 4]
8   [10, 6, 4]
9   [11, 5, 4]
10  [12, 4, 4]
11  [9, 8, 3]
12  [10, 7, 3]
13  [11, 6, 3]
14  [12, 5, 3]
15  [13, 4, 3]
16  [14, 3, 3]
17  [9, 9, 2]
18  [10, 8, 2]
19  [11, 7, 2]
20  [12, 6, 2]
21  [13, 5, 2]
22  [14, 4, 2]
23  [15, 3, 2]
24  [16, 2, 2]
25  [10, 9, 1]
26  [11, 8, 1]
27  [12, 7, 1]
28  [13, 6, 1]
29  [14, 5, 1]
30  [15, 4, 1]
31  [16, 3, 1]
32  [17, 2, 1]
33  [18, 1, 1]

Я оставлю это OP, чтобы убедиться, что этот код действительно генерирует разделы в соответствии с желаемым (равномерным) распределением.

РЕДАКТИРОВАТЬ: добавлен пример функциональности перечисления.

person mhum    schedule 17.02.2013
comment
Я сравнил вывод функции с выходом функции, заведомо непредвзятой, но более медленной. Я сгенерировал случайные выборки из каждой функции и сравнил распределение статистических признаков (асимметрия, четность, среднее значение слагаемого), как в goo.gl/uNWR4. Если бы функция была несмещенной, полученные кривые плотности ядра не выявили бы каких-либо систематических различий. Как бы то ни было, они близки, но различны. Итак, закралась предвзятость. Завтра я потрачусь на изучение того, что вы сделали (что очень круто), и попытаюсь найти, в чем заключается предвзятость. Я могу отправить по электронной почте/опубликовать вывод/результаты/скрипт, если хотите. - person klocey; 18.02.2013
comment
В порядке. Я протестировал только несколько случаев, когда N и K были достаточно малы, чтобы получить достойные выборки для всех возможных разделов. Можете ли вы описать расхождения? А для какого N&K вы тестировали? Я сделаю еще один проход и попытаюсь отладить. - person mhum; 18.02.2013
comment
Я использовал N={20,40} и K={3,5,10}, достаточно малые, чтобы изучить особенности распределения допустимых наборов (т. е. всех разделов для N&K), которые я сравнил, используя кривые плотности ядра, со случайными выборками, сгенерированными w/ ваш метод. Статистическая равномерность была выше, чем должна быть. Асимметрия часто была ниже, чем должна быть. Среднее значение слагаемого показало мультимодальность, но должно быть унимодальным. Кривые плотности для случайных выборок хорошо согласовывались друг с другом, но не с возможными множествами. - person klocey; 18.02.2013
comment
Хм. Я перепроверил и не смог найти никаких ошибок. Вы уверены в своей статистике? Например, срединное слагаемое не всегда одномодально. Для N = 40, K = 10 существует 791 раздел с медианой 2, 422 с 2,5 и 922 с 3. Я отредактировал функцию generate(), чтобы обеспечить режим перечисления, чтобы вы могли убедиться, что он обеспечивает биекцию между разбиения и целые числа 1..B[N][K]. - person mhum; 18.02.2013
comment
Я не знаю. Кривые Kdensity для допустимых наборов и случайных выборок должны хорошо согласовываться. Другие гораздо более медленные алгоритмы показывают высокое согласие при сравнении кривых kdensity (модальность зависит от ширины ядра). Может ли быть врожденная предвзятость в том, что вы пытаетесь сделать? Мои сравнения основаны на коде, использованном в моем препринте на figshare, где кривые kdensity для случайных выборок с использованием двух разных алгоритмов неразличимы, а результаты не показывают смещения по сравнению с возможными наборами. Может я неправильно реализовал ваш код? Я могу опубликовать необходимые функции для сравнения. - person klocey; 19.02.2013
comment
Наверное, я не совсем понимаю ваш метод сравнения. Например, почему вы используете оценки плотности ядра, когда для таких малых N и K вы можете явно перечислить все разделы и вычислить эту статистику (медиану, перекос и т. д.) напрямую? В любом случае, вы можете проверить в моем обновленном коде, что для каждого r, generate(N,K,r) создает уникальный раздел N в K частях (и все такие разделы представлены), таким образом, однородная выборка из r должна производить однородную выборку со всех таких разделов. - person mhum; 19.02.2013
comment
Использование кривых kdensity может быть неясным. Это может помочь: 1) найти четность, асимметрию и т. д. для допустимого множества (т. е. каждого раздела для N&S). 2) сгенерировать частотное распределение (kdensity), выявляющее равномерность, асимметрию и т. д. по допустимому набору. 3) сравнить кривую kdensity для допустимого множества с кривыми kdensity для случайных выборок, каждая из которых состоит из множества случайно сгенерированных разделов. Если случайные выборки несмещены, кривые kdensity для них должны лежать на кривых допустимого множества. Подход обеспечивает визуальное сравнение и показывает, где и как метод дает сбой. - person klocey; 19.02.2013
comment
В порядке. Часть, которую я не думаю, что понимаю, заключается в том, почему вы должны использовать оценку плотности ядра частотного распределения вместо самого частотного распределения? Я не думал, что вам нужно что-то оценивать, поскольку точное распределение известно посредством перечисления. На самом деле, для малых N&K вам может и не понадобиться сравнивать эти статистические данные. Вы можете просто проверить однородность количества сгенерированных разделов (скажем, с помощью критерия хи-квадрат). Например: если вы создадите 33000 разделов для N=20, K=3, вы должны ожидать увидеть около 1000 каждого раздела. - person mhum; 19.02.2013
comment
Да, вы совершенно правы. Тем не менее, я использую кривые kdensity, потому что обычно я сравниваю сотни частотных распределений (случайный алгоритм 1) с сотнями других (случайный алгоритм 2) и ищу систематическое смещение направления или странное модальное поведение ... и часто для больших N & К. Итак, очевидное смещение кривых kdensity предполагает, что что-то не так и, возможно, как/где это неправильно. Но высокое согласие не обязательно означает, что все правильно... вот почему я смотрю на несколько статистических характеристик (равномерность, асимметрия, медианное слагаемое, коэффициент Джини и т. д.). - person klocey; 19.02.2013

Ниже приведен сценарий, который выполняет то, о чем я просил, в том, что касается перекодирования целых чисел, представляющих целочисленные разделы N с K частями. Чтобы этот подход был практичным для K > 4, необходим лучший метод перекодирования. Это определенно не лучший или предпочтительный подход. Тем не менее, он концептуально прост, и его легко аргументировать как фундаментально беспристрастный. Это также очень быстро для небольших K. Сценарий отлично работает в блокноте Sage и не вызывает функции Sage. Это НЕ скрипт для случайной выборки. Случайная выборка сама по себе не является проблемой.

Метод:

1.) Рассматривайте целочисленные разделы так, как будто их слагаемые объединены вместе и дополнены нулями в соответствии с размером наибольшего слагаемого в первом лексическом разделе, например. [17,1,1,1] -> 17010101 и [5,5,5,5] -> 05050505

2.) Рассматривайте полученные целые числа так, как если бы они вычитались из наибольшего целого числа (т. е. int, представляющего первый лексический раздел). например 17010101 - 5050505 = 11959596

3.) Рассматривайте каждое полученное уменьшенное целое число как разделенное на общий знаменатель, например. 11959596/99 = 120804

Итак, если бы мы хотели выбрать случайный раздел, мы бы:

1.) Выберите число от 0 до 120 804 (вместо числа от 5 050 505 до 17 010 101)

2.) Умножьте число на 99 и вычтите из 17010101

3.) Разделите полученное целое число в соответствии с тем, как мы обрабатывали каждое целое число как дополненное нулями.

За и против: Как указано в тексте вопроса, этот конкретный метод перекодирования не делает достаточно, чтобы значительно повысить вероятность случайного выбора целого числа, представляющего элемент P. Для небольшого количества частей , например K ‹ 5 и существенно большие суммы, т.е. При N > 100 функция, реализующая эту концепцию, может быть очень быстрой, поскольку такой подход позволяет избежать своевременной рекурсии (змея, пожирающая свой хвост), которая замедляет другие случайные функции распределения или делает другие функции непрактичными для работы с большими N.

При малом K вероятность рисования члена P может быть разумной, если учесть, насколько быстрым является оставшийся процесс. В сочетании с быстрым случайным розыгрышем, декодированием и оценкой эта функция может находить равномерные случайные разбиения для комбинаций N&K (например, N = 20000, K = 4), которые несовместимы с другими алгоритмами. Чтобы сделать этот подход эффективным, крайне необходим лучший способ перекодирования целых чисел.

import random
import sys

Во-первых, несколько полезных и простых функций

def first_partition(N,K):
    part = [N-K+1]
    ones = [1]*(K-1)
    part.extend(ones)
return part

def last_partition(N,K):
    most_even = [int(floor(float(N)/float(K)))]*K
    _remainder = int(N%K)
    j = 0

    while _remainder > 0:
        most_even[j] += 1
        _remainder -= 1
        j += 1
    return most_even

def first_part_nmax(N,K,Nmax):
    part = [Nmax]
    N -= Nmax
    K -= 1
    while N > 0:
        Nmax = min(Nmax,N-K+1)
        part.append(Nmax)
        N -= Nmax
        K -= 1

    return part


#print first_partition(20,4)
#print last_partition(20,4)
#print first_part_nmax(20,4,12)
#sys.exit()

def portion(alist, indices): 
    return [alist[i:j] for i, j in zip([0]+indices, indices+[None])]

def next_restricted_part(part,N,K): # *find next partition matching N&K w/out recursion

    if part == last_partition(N,K):return first_partition(N,K)
    for i in enumerate(reversed(part)):
        if i[1] - part[-1] > 1:
            if i[0] == (K-1):
                return first_part_nmax(N,K,(i[1]-1))

            else:
                parts = portion(part,[K-i[0]-1]) # split p
                h1 = parts[0]
                h2 = parts[1]
                next = first_part_nmax(sum(h2),len(h2),(h2[0]-1))
                return h1+next

""" *I don't know a math software that has this function and Nijenhuis and Wilf (1978) 
 don't give it (i.e. NEXPAR is not restricted by K). Apparently, folks often get the
 next restricted part using recursion, which is unnecessary """

def int_to_list(i): # convert an int to a list w/out padding with 0'
    return [int(x) for x in str(i)]

def int_to_list_fill(i,fill):# convert an int to a list and pad with 0's
    return [x for x in str(i).zfill(fill)]

def list_to_int(l):# convert a list to an integer
    return "".join(str(x) for x in l)

def part_to_int(part,fill):# convert an int to a partition of K parts
# and pad with the respective number of 0's

    p_list = []
    for p in part:
        if len(int_to_list(p)) != fill:
            l = int_to_list_fill(p,fill)
            p = list_to_int(l)
        p_list.append(p)
    _int = list_to_int(p_list)
    return _int       

def int_to_part(num,fill,K): # convert an int to a partition of K parts
    # and pad with the respective number of 0's
    # This function isn't called by the script, but I thought I'd include
    # it anyway because it would be used to recover the respective partition

    _list = int_to_list(num)
    if len(_list) != fill*K:
        ct = fill*K - len(_list) 
        while ct > 0:    
            _list.insert(0,0)
            ct -= 1    
    new_list1 = []
    new_list2 = []

    for i in _list:
        new_list1.append(i)
        if len(new_list1) == fill:
            new_list2.append(new_list1)
            new_list1 = []

    part = []
    for i in new_list2:
        j = int(list_to_int(i))
        part.append(j)

    return part

Наконец, мы получаем общее количество N и количество частей K. Далее будут напечатаны разделы, удовлетворяющие N&K, в лексическом порядке с соответствующими перекодированными целыми числами

N = 20
K = 4

print '#,  partition,  coded,    _diff,    smaller_diff'

first_part = first_partition(N,K) # first lexical partition for N&K
fill = len(int_to_list(max(first_part)))
# pad with zeros to 1.) ensure a strictly decreasing relationship w/in P,
# 2.) keep track of (encode/decode) partition summand values

first_num = part_to_int(first_part,fill)
last_part = last_partition(N,K)
last_num = part_to_int(last_part,fill)

print '1',first_part,first_num,'',0,'      ',0

part = list(first_part)
ct = 1
while ct < 10:
    part = next_restricted_part(part,N,K)
    _num = part_to_int(part,fill)

    _diff = int(first_num) - int(_num)
    smaller_diff = (_diff/99)
    ct+=1

    print ct, part, _num,'',_diff,' ',smaller_diff

ВЫВОД:

ct, раздел, закодированный, _diff, small_diff

1 [17, 1, 1, 1] 17010101 0 0

2 [16, 2, 1, 1] 16020101 990000 10000

3 [15, 3, 1, 1] 15030101 1980000 20000

4 [15, 2, 2, 1] 15020201 1989900 20100

5 [14, 4, 1, 1] 14040101 2970000 30000

6 [14, 3, 2, 1] 14030201 2979900 30100

7 [14, 2, 2, 2] 14020202 2989899 30201

8 [13, 5, 1, 1] 13050101 3960000 40000

9 [13, 4, 2, 1] 13040201 3969900 40100

10 [13, 3, 3, 1] 13030301 3979800 40200

Короче говоря, целые числа в последнем столбце могут быть намного меньше.

Почему стратегия случайной выборки, основанная на этой идее, принципиально беспристрастна:

Каждая целочисленная часть из N, состоящая из K частей, соответствует одному и только одному перекодированному целому числу. То есть мы не выбираем число наугад, декодируем его, а затем пытаемся переставить элементы, чтобы сформировать правильный раздел N&K. Следовательно, каждое целое число (независимо от того, соответствует ли оно разбиению N&K или нет) имеет одинаковую вероятность выпадения. Цель состоит в том, чтобы изначально уменьшить количество целых чисел, не соответствующих разбиению N на K частей, и, таким образом, ускорить процесс случайной выборки.

person Community    schedule 18.02.2013
comment
Я все еще немного не понимаю, почему вы считаете, что правильный подход состоит в том, чтобы улучшить вашу кодировку конкатенации, а не придумывать лучшую начальную кодировку. Похоже, что ваше предыдущее решение , а также решение, которое я здесь представляю, вычисляют взаимное соответствие между разделами N с K частями и [1..M], где M — общее количество таких разделов. Вы не можете получить более компактную кодировку. - person mhum; 19.02.2013
comment
Я бы не сказал, что считаю это правильным подходом. Это просто метод, который я использовал для создания равномерных случайных разбиений N с K частями без рекурсии, который является упрощенным, принципиально беспристрастным и беспристрастным в том виде, в котором он реализован. Конечно, это безумно непрактично, когда K > 4. Опять же, идея состоит в том, что рекурсия является врагом для некоторых проблем случайного разбиения. - person klocey; 19.02.2013
comment
Ваше предыдущее решение и решение, которое я описываю здесь, в основном одинаковы, за исключением того, что я использую немного другую рекурсию. Проблемы, которые у вас возникли при выполнении вашего предыдущего решения (скорость, разрушение стека), вероятно, можно смягчить, если вы сделаете то, что сделал я, и просто предварительно вычислите D итеративно. - person mhum; 19.02.2013
comment
Это может сработать. Насколько я мог судить, ваш способ сделать это был довольно быстрым и работал очень хорошо. Я попробую. Спасибо! - person klocey; 19.02.2013
comment
Я не уверен, что вы имеете в виду здесь. Вы имеете в виду рекурсию в смысле программирования или в математическом смысле? Хотя в моей реализации не используется программная рекурсия, вычисление количества разделов по-прежнему определяется рекурсивно (то есть: B[n][k] = B[nk][k] + B[n-1][k-1] ). Хотя у вас были проблемы с (программной) рекурсией, разрушающей ваш стек в вашей предыдущей реализации, как я уже сказал, вы обычно можете развернуть эти вещи. - person mhum; 19.02.2013
comment
И, если вы имеете в виду программную рекурсию, я бы снова отослал вас к Nijenhuis and Wilf, где весь код написан на Fortran 4, который вообще не допускает рекурсии. Итак, люди уже некоторое время генерируют комбинаторные объекты без рекурсии. - person mhum; 19.02.2013
comment
Я имею в виду функцию, которая вызывает себя, чтобы получить некоторое начальное значение, а затем передает значения обратно по цепочке, как во многих реализованных функциях разделения. Я полагаю, мне нравится идея получить однородный случайный раздел без рекурсии и без перебора возможных значений слагаемых или кратностей. Лучший (единственный) способ, который я нашел, чтобы, скажем, получить однородную случайную выборку разделов для 400 000 с 4 частями, - это рассматривать разделы как объект другого типа (например, перекодированное целое число). Возможно, реализация вашего способа получения D в моем предыдущем алгоритме (figshare) сделает это. - person klocey; 19.02.2013
comment
Удаление комментария, на который вы ссылались, было коленным рывком (то есть, черт возьми, я совершенно не прав). Я не видел, чтобы ты хорошо ответил на это. Извинения. - person klocey; 19.02.2013
comment
В порядке. В Fortran не было рекурсии до Fortran 90 (хотя были хакерские обходные пути), поэтому все алгоритмы в Nijenhuis и Wilf нерекурсивны. Кроме того, когда я смотрю на ваш предыдущая реализация, я думаю, что вы действительно эффективно закодировали разделы в целые числа - вы берете целое число от 1 до N (переменная 'what') и превращаете его в раздел. Я думаю, что ваша проблема заключается в неэффективном вычислении D. - person mhum; 19.02.2013
comment
Кроме того, вы могли бы добиться большего успеха с немного другим рекурсивным определением. Я взял свой прямо у Nijenhuis and Wilf, и у него всего два индекса. Ваш немного сложнее с 3 индексами. Но я не уверен, что это будет иметь большое значение. - person mhum; 19.02.2013