Случайный Гольф Дня № 3: Целочисленные Перегородки

19

О серии

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

Несмотря на то, что у меня есть ряд идей для этой серии, будущие проблемы еще не заложены. Если у вас есть какие-либо предложения, пожалуйста, сообщите мне об этом в соответствующей песочнице .

Отверстие 3: целочисленные разделы

Время немного увеличить сложность.

Разбиение положительного целого числа n, определяется как мультимножестве положительных целых чисел, сумма которых равна n. Например, если n = 5существуют следующие разделы:

{1,1,1,1,1}
{2,1,1,1}
{2,2,1}
{3,1,1}
{3,2}
{4,1}
{5}

Обратите внимание , что это мультимножествами, так что нет никакого порядка им {3,1,1}, {1,3,1}и {1,1,3}все они считаются идентичными.

Ваша задача состоит в том, nчтобы создать случайный раздел n. Вот подробные правила:

  • Распределение произведенных перегородок должно быть равномерным . То есть в приведенном выше примере каждый раздел должен быть возвращен с вероятностью 1/7.

    Конечно, из-за технических ограничений PRNG идеальная однородность будет невозможна. Для оценки единообразия вашего представления следующие операции будут рассматриваться как обеспечивающие абсолютно равномерное распределение:

    • Получение номера из PRNG (в любом диапазоне), который задокументирован, чтобы быть (приблизительно) равномерным.
    • Отображение равномерного распределения по большому набору чисел в меньший набор по модулю или умножению (или какой-либо другой операции, которая распределяет значения равномерно). Большой набор должен содержать как минимум в 1024 раза больше возможных значений, чем меньший набор.
  • Поскольку разделы являются мультимножествами, вы можете возвращать их в любом порядке, и этот порядок не обязательно должен быть согласованным. Однако в целях случайного распределения порядок игнорируется. То есть, в приведенном выше примере, {3,1,1}, {1,3,1}и {1,1,3} вместе должны иметь вероятность 1/7 возвращается.

  • Ваш алгоритм должен иметь детерминированную среду выполнения. В частности, вы не можете генерировать случайные мультимножества и отклонять их, если они не суммируются n.
  • Временная сложность вашего алгоритма должна быть полиномиальной n. В частности, вы не можете просто сгенерировать все разделы и выбрать случайный (так как количество разделов растет в геометрической прогрессии n). Вы можете предположить, что используемый PRNG может возвращать равномерно распределенные значения в O (1) на значение.
  • Вы не должны использовать какую-либо встроенную функцию, которая решает эту задачу.

Вы можете написать полную программу или функцию и получить ввод через STDIN или ближайшую альтернативу, аргумент командной строки или аргумент функции и произвести вывод через возвращаемое значение или распечатать в STDOUT (или ближайшую альтернативу).

Вы можете предположить, что n ≤ 65(например, количество разделов меньше, чем 21 ). Вывод может быть в любом удобном, однозначном списке или строковом формате.

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

Это код гольф, поэтому выигрывает самое короткое представление (в байтах). И, конечно же, самая короткая заявка на пользователя также войдет в общую таблицу лидеров серии.

Leaderboard

Первый пост серии генерирует таблицу лидеров.

Чтобы убедиться, что ваши ответы отображаются, начните каждый ответ с заголовка, используя следующий шаблон уценки:

# Language Name, N bytes

где Nразмер вашего представления. Если вы улучшите свой счет, вы можете сохранить старые результаты в заголовке, вычеркнув их. Например:

# Ruby, <s>104</s> <s>101</s> 96 bytes

(Язык в настоящее время не отображается, но фрагмент требует и анализирует его, и я могу добавить таблицу лидеров по языкам в будущем.)

Мартин Эндер
источник

Ответы:

8

Python 2, 179 байт

from random import*
m=r=input();i=q=r+1;h=[1]+[0]*q*q;exec"h[i]=h[i+~q]+h[i-i%q*q];i+=1;"*r*q
while r:
 x=random()*sum(h[r*q:r*q-~m]);m=0
 while x>0:m+=1;x-=h[r*q+m]
 print m;r-=m

Я использовал формулу (39) из этого экстракта Кнута , которая дает количество разделов, nкоторые имеют ровно mчасти. Это происходит равным количеству разделов, nкоторые имеют mв качестве максимального элемента, что является интерпретацией, которую я использую. Элементы раздела генерируются от наибольшего к наименьшему. На каждом этапе формула используется повторно с текущим остатком nи максимально допустимым элементом.

feersum
источник
5

Дьялог АПЛ, 67 59 51 байт

p←{⍵,⊂1,⍨+/¨⌽⍵↑¨⍨⌽⍳⍴⍵}⍣⎕⊢⍬⋄f←{⍵=0:⍬⋄a,a∇⍵-a←{1++/(?+/⍵)>+\⍵}⍺↑⍵⊃p}⍨ (67 байт)

p- вектор векторов, в котором p[n][k]есть число разбиений nна kслагаемые или, что эквивалентно: число разбиений с наибольшим слагаемым k. Мы строим p, начиная с пустого вектора , читая n( чтение reads) и многократно применяя следующее:

{⍵,⊂1,⍨+/¨⌽⍵↑¨⍨⌽⍳⍴⍵}
                 ⍴⍵   ⍝ the current length, initially 0
                ⍳⍴⍵   ⍝ 1 2 ... length
               ⌽⍳⍴⍵   ⍝ length ... 2 1
           ⍵↑¨⍨       ⍝ take length elements from p[1], length-1 from p[2], etc
                      ⍝ padded with 0-s, e.g. if p was (,1)(1 1)(1 1 1)(1 2 1 1)(1 2 2 1 1):
                      ⍝ we get:     (1 0 0 0 0)(1 1 0 0)(1 1 1)(1 2)(,1)
          ⌽           ⍝ reverse it: (,1)(1 2)(1 1 1)(1 1 0 0)(1 0 0 0 0)
       +/¨            ⍝ sum each:   1 3 3 2 1
    1,⍨               ⍝ append 1:   1 3 3 2 1 1
 ⍵,⊂                  ⍝ append the above to the vector of vectors

После nприложений ( ⍣⎕) мы построили p.

fвыбирает случайный раздел. n f kслучайное разбиение не более чем k слагаемых. f nесть n f n.

{⍵=0:⍬⋄a,a∇⍵-a←{1++/(?+/⍵)>+\⍵}⍺↑⍵⊃p}⍨
                                     ⍨ ⍝ "selfie" -- use n as k if no k is provided
 ⍵=0:⍬                                 ⍝ if n=0 return empty
                                 ⍵⊃p   ⍝ pick the n-th element of p
                               ⍺↑      ⍝ take k elements from that
               {1++/(?+/⍵)>+\⍵}        ⍝ use them as weights to pick a random number 1...k
               {           +\⍵}        ⍝   partial sums of weights
               {    (?+/⍵)    }        ⍝   a random number 1...sum of weights
               {    (?+/⍵)>+\⍵}        ⍝   which partial sums is it greater than?
               {  +/          }        ⍝   count how many "greater than"-s
               {1+            }        ⍝   we're off by one
             a←                        ⍝ this will be the greatest number in our partition
         a∇⍵-a                         ⍝ recur with n1=n-a and k1=a
       a,                              ⍝ prepend a

Некоторые улучшения:

  • встроенный pза счет немного худшей (но все же достаточно хорошей) производительности

  • в расчете pпереставить и 1,сохранить персонажа

  • свернуть {1++/(?+/⍵)>+\⍵}в поезд с 1+впереди:1+(+/(?+/)>+\)

  • сделать fанонимную функцию и предоставить (eval'ed input) в качестве аргумента для получения полной программы

{⍵=0:⍬⋄a,a∇⍵-a←1+(+/(?+/)>+\)⍺↑⍵⊃{⍵,⊂⌽1,+/¨⍵↑¨⍨⌽⍳⍴⍵}⍣⍵⊢⍬}⍨⎕ (59 байт)

Тест с n = 5

Тест с n = 65

И следующая ссылка запускается n = 5 тысяч раз и собирает статистику о частоте каждого раздела: ⎕rl←0 ⋄ {⍺,⍴⍵}⌸ {⍵=0:⍬⋄a,a∇⍵-a←1+(+/(?+/)>+\)⍺↑⍵⊃{⍵,⊂⌽1,+/¨⍵↑¨⍨⌽⍳⍴⍵}⍣⍵⊢⍬}⍨ ¨10000⍴5


Дополнительные улучшения с помощью Роджера Хуэя :

  • заменить {⍵=0:A⋄B}на {×⍵:B⋄A}. Signum ( ×⍵) возвращает true для ⍵>0и false для ⍵=0.

  • заменить (+/(?+/)>+\)на +/b<?⊃⌽b←+\, это сохраняет характер

  • используйте матрицу вместо вектора векторов для вычисления p: замените ⍵⊃{⍵,⊂⌽1,+/¨⍵↑¨⍨⌽⍳⍴⍵}⍣⍵⊢⍬на ⊃↓(0,⍨⊢⍪⍨1 1⍉+\)⍣⍵⍪1.

{×⍵:a,a∇⍵-a←1++/b<?⊃⌽b←+\⍺↑⊃↓(0,⍨⊢⍪⍨1 1⍉+\)⍣⍵⍪1⋄⍬}⍨ (51 байт)

испытание n = 5 ; испытание n = 65 ; частота статистика

СПП
источник
2
Как получить помощь от Роджера Хуэя?
FUZxxl
5
Напишите игрушечного переводчика APL, чтобы получить работу в той же компании, что и он. Поставьте вышеупомянутое выражение как вызов, пообещайте пинту пива за каждого персонажа, которого он уберет. Тогда прибыль: меньше персонажей и больше выпивки, потому что он не пьет пиво.
августа
1
Понимаю. Это изящная стратегия, давайте посмотрим, смогу ли я воспроизвести это ... Можете ли вы спросить его, получит ли Dyalog APL что-то похожее на J в u/\. yближайшее время?
FUZxxl
Спасибо, что спросили его. Теперь мне интересно, возможно ли это и за линейное время.
FUZxxl
4

GolfScript, 90 байт

~[[[1.]]]\({..[[{{(\{)}%+}%1$,1$,-=}%[1,]@0=+{1+}%]zip{{(\.,/*~}%.,.rand@=+}:^%]\+}*0=^(;`

Онлайн демо

Это адаптация моего (более простого) кода подсчета разделов, который вместо простого отслеживания счетчика отслеживает как счетчик, так и равномерно выбранный из подсчитанных элементов.

Совместное сравнение двух:

~[[[1.]]]\({..[[{{(\{)}%+}%1$,1$,-=}%[1,]@0=+{1+}%]zip{{(\.,/*~}%.,.rand@=+}:^%]\+}*0=^(;`
 [[ 1  ]]\({..[[{          1$,1$,-=}%  0 @0=+     ]zip{{+}*                }:^%]\+}*0=^

Отличия:

  • Первоначально ~, потому что это программа, а не фрагмент.
  • В [1.]замещающие 1соответствует изменению в том, что это отслеживается.
  • Дополнительный {(\{)}%+}%увеличивает каждый элемент в этом разделе, и {1+}%добавляет 1к разделу.
  • 0становится [0](гольфом 1,) как часть изменения того, что отслеживается, но потому что оно должно оставаться массивом при добавлении к другому, ему нужно дополнительное [ ].
  • Простая сумма {+}*становится взвешенным выбором из разделов в сочетании с суммированием их количества.
  • The (;`удаляет счетчик из вывода и помещает раздел в хороший формат.

Тестовая структура

;7000,{;
  '5'

  ~[[[1.]]]\({..[[{{(\{)}%+}%1$,1$,-=}%[1,]@0=+{1+}%]zip{{(\.,/*~}%.,.rand@=+}:^%]\+}*0=^(;`

}%
:RESULTS
.&${
  RESULTS.[2$]--,' '\n
}/

Настройте начальные 7000, если вы хотите запустить другое количество испытаний. Обратите внимание, что это слишком медленно для онлайн-демонстрации.

Питер Тейлор
источник
3

Java, 285 267 байт

int[][]p;void p(int n){p=new int[n+1][n+1];int a=n,b=k(n,a),c,d;for(b*=Math.random();n>0;System.out.print(c+" "),n-=a=c)for(c=0;c++<(a<n?a:n)&b>=(d=k(n-c,c));b-=d);}int k(int n,int k){if(p[n][k]<1)for(int a=0,b=0;b<k&b++<n;p[n][k]=a)a+=k(n-b,b);return n>0?p[n][k]:1;}

Это тот же метод, что и в ответе TheBestOne, но он использует простой массив вместо карты. Кроме того, вместо того, чтобы возвращать случайный раздел какList , он выводит их на консоль.

Ниже приведена тестовая программа, которая запускает ее 100000 раз. Например n=5, все подходы были в пределах 0,64% от идеальной 1/7 в моем последнем запуске.

public class Partition {
    public static void main(String[] args) {
        Partition p = new Partition();
        for(int i=0;i<100000;i++){
            p.p(5);
            System.out.println();
        }
    }

    int[][]p;

    void p(int n){
        p=new int[n+1][n+1];
        int a=n,b=k(n,a),c,d;
        for(b*=Math.random();n>0;System.out.print(c+" "),n-=a=c)
            for(c=0;c++<(a<n?a:n)&b>=(d=k(n-c,c));b-=d);
    }

    int k(int n,int k){
        if(p[n][k]<1)
            for(int a=0,b=0;b<k&b++<n;p[n][k]=a)
                a+=k(n-b,b);
        return n>0?p[n][k]:1;
    }

}
Geobits
источник
3
Хотя вы golfed на Math.minвызов вплоть до (k<n?k:n), вы можете пойти дальше, канав его полностью и просто делать две проверки: b<k&b++<n. Вы также можете легко отказаться n>0от условной части цикла (поскольку n>0&b<nсводится к тому, b<nкогда bгарантированно неотрицательный).
Питер Тейлор
@PeterTaylor Спасибо. Еще один взгляд, позвольте мне избавиться от дополнительного оператора return и отдельного intобъявления.
Geobits
3

CJam, 64 56 байт

ri_L{_0>{\,f{)_@1$-j+}{)@)2$+:Umr@<@@?U+}*}{!a\;}?}2j);p

Вы можете проверить это с помощью этого скрипта:

ria100*{_L{_0>{\,f{)_@1$-j+}{)@)2$+:Umr@<@@?U+}*}{!a\;}?}2j);}%__|\f{_,\2$a-,-}2/p

объяснение

ri_                  " Read an integer and duplicate. ";
L{                   " Create a memoized function of the maximum and the sum, which returns
                       a random partition, and the total number of partitions as the last item. ";
    _0>              " If sum > 0: ";
    {
        \,f{         " For I in 0..max-1: ";
            )_@1$-   " Stack: I+1 I+1 sum-I-1 ";
            j+       " Recursively call with the two parameters, and prepend I+1. ";
        }
        {            " Reduce on the results: ";
            )@)2$+   " Stack: partition1 total1 partition2 total1+total2 ";
            :Umr     " U = total1+total2, then generate a random number smaller than that. ";
            @<@@?    " If it is <total1, choose partition1, else choose partition2. ";
            U+       " Append the total back to the array. ";
        }*
    }
    {!a\;}?          " Else return [0] if negative, or [1] if zero. ";
}2j
);p                  " Discard the total and print. ";
jimmy23013
источник
2
Вы должны удалить неправильную часть «не очень хорошо» в вашем ответе;)
anatolyg
@anatolyg Удалено. Но я считаю, что все еще возможно удалить некоторые байты. Мне просто лень это делать.
jimmy23013
3

Pyth, 64 байта

Использует /programming//a/2163753/4230423 за исключением того, что a) нет кэша, поскольку Pyth автоматически запоминает, b) печатает каждый вместо добавления в список, и c) переводится в Pyth.

M?smg-Gddr1hhS,GHG1Akd,QOgQQWQFNr1hhS,QkKg-QNNI<dKB-=dK)N=kN-=QN

Я опубликую объяснение этого, когда у меня будет время, но вот соответствующий код Python:

g=lambda G,H: sum(map(lambda d:g(G-d, d), range(1, (H if H<G else G) + 1))) if G else 1
Q=input()
k,d = Q,random.randrange(g(Q, Q))
while Q:
    for N in range(1, min(k, Q) + 1):
        K = g(Q-N, N)
        if d < K:
            break
        d -= K
    print N
    k=N
    Q -= N

Изменить: я наконец-то нашел время для объяснения:

M                Lambda g(G,H)
 ?         G     If G truthy
  s              Sum
   m             Map
    g            Recursive call
     -Gdd        G-d,d
    r            Range
     1           1 to
     h           +1
      hS         First element of sorted (does min)
       ,GH       From G and H
   1             Else 1
A                Double assign
 kd              Vars k and d
 ,               To vals
  Q              Q (evaled input)
  O              Randrange 0 till val
   gQQ           Call g(Q, Q)
WQ               While Q is truthy
 FN              For N in
  r              Range
   1             From one
   h             Till +1
    hS,QK        Min(Q,K)
  Kg             K=g(
   -QN           Q-N
   N             N
  I<dK           If d<k
   B             Break (implicit close paren)
  -=dk           Subtracts d-=k
 )               Close out for loop
 N               Prints N
 =kN             Set k=N
 -=QN            Subtracts Q-=N
Maltysen
источник
2

Октава, 200

function r=c(m)r=[];a=eye(m);a(:,1)=1;for(i=3:m)for(j=2:i-1)a(i,j)=a(i-1,j-1)+a(i-j,j);end;end;p=randi(sum(a(m,:)));while(m>0)b=a(m,:);c=cumsum(b);x=min(find(c>=p));r=[r x];p=p-c(x)+b(x);m=m-x;end;end

Ungolfed:

function r=c(m)
  r=[];
  a=eye(m);
  a(:,1)=1;
  for(i=3:m)
    for(j=2:i-1)
      a(i,j)=a(i-1,j-1)+a(i-j,j);
    end;
  end;
  p=randi(sum(a(m,:)));
  while(m>0)
    b=a(m,:);
    c=cumsum(b);
    x=min(find(cumsum(b)>=p));
    r=[r x];
    p=p-c(x)+b(x);
    m=m-x;
  end
end

Постройте квадратную матрицу, где каждая ячейка (m, n) отражает количество разделов m, наибольшее число которых n, согласно любезно процитированному Knuth extract @feersum. Например, 5,2дает нам 2, потому что есть два допустимых раздела 2,2,1и 2,1,1,1. 6,3дает нам 3 для 3,1,1,1, 3,2,1и 3,3.

Теперь мы можем детерминистически найти p-й раздел. Здесь мы генерируем pслучайное число, но вы можете немного изменить сценарий, так pчто это параметр:

function r=c(m,p)
  r=[];
  a=eye(m);
  a(:,1)=1;
  for(i=3:m)
    for(j=2:i-1)
      a(i,j)=a(i-1,j-1)+a(i-j,j);
    end;
  end;
  while(m>0)
    b=a(m,1:m);
    c=cumsum(b);
    x=min(find(c>=p));
    r=[r x];
    p=p-c(x)+b(x);
    m=m-x;
  end
end

Теперь мы можем детерминистически показать, что каждый результат зависит исключительно от p:

octave:99> for(i=1:7)
> c(5,i)
> end
ans =

   1   1   1   1   1

ans =

   2   1   1   1

ans =

   2   2   1

ans =

   3   1   1

ans =

   3   2

ans =

   4   1

ans =  5

Таким образом, возвращаясь к оригиналу, где p генерируется случайным образом, мы можем быть уверены, что каждый результат одинаково вероятен.

dcsohl
источник
Я не уверен в вашем 5,2 примере. Не должно быть двух разделов (2,2,1)и (2,1,1,1,1)(так как у двух перечисленных вами номеров больше, чем 2).
Мартин Эндер
Вы правы, я все перепутал. Есть два раздела с двумя компонентами и два раздела, начинающиеся с 2. Я имел ввиду последнее.
dcsohl
2

R, 198 байт

function(m){r=c();a=diag(m);a[,1]=1;for(i in 3:m)for(j in 2:(i-1))a[i,j]=a[i-1,j-1]+a[i-j,j];p=sample(sum(a[m,]),1);while(m>0){b=a[m,];c=cumsum(b);x=min(which(c>=p));r=c(r,x);p=p-c[x]+b[x];m=m-x};r}

Ungolfed:

f <- function(m) {
    r <- c()
    a <- diag(m)
    a[, 1] <- 1
    for (i in 3:m)
        for (j in 2:(i-1))
            a[i, j] <- a[i-1, j-1] + a[i-j, j]
    p <- sample(sum(a[m, ]), 1)
    while (m > 0) {
        b <- a[m, ]
        c <- cumsum(b)
        x <- min(which(c >= p))
        r <- c(r, x)
        p <- p - c[x] + b[x]
        m <- m - x
    }
    return(r)
}

Он следует той же структуре, что и отличное решение @ dcsohl в Octave , и, следовательно, также основан на экстракте Кнута, опубликованном @feersum.

Я отредактирую это позже, если смогу найти более креативное решение в R. Тем временем, любой вклад, конечно, приветствуется.

Алекс А.
источник
1

Java, 392 байта

import java.util.*;Map a=new HashMap();List a(int b){List c=new ArrayList();int d=b,e=b(b,d),f=(int)(Math.random()*e),g,i;while(b>0){for(g=0;g++<Math.min(d, b);f-=i){i=b(b-g,g);if(f<i)break;}c.add(g);d=g;b-=g;}return c;}int b(int b,int c){if(b<1)return 1;List d=Arrays.asList(b,c);if(a.containsKey(d))return(int)a.get(d);int e,f;for(e=f=0;f++<Math.min(c, b);)e+=b(b-f,f);a.put(d,e);return e;}

Позвони с a(n). Возвращает a Listиз Integers

Отступ:

import java.util.*;

Map a=new HashMap();

List a(int b){
    List c=new ArrayList();
    int d=b,e=b(b,d),f=(int)(Math.random()*e),g,i;
    while(b>0){
        for(g=0;g++<Math.min(d, b);f-=i){
            i=b(b-g,g);
            if(f<i)
                break;
        }
        c.add(g);
        d=g;
        b-=g;
    }
    return c;
}

int b(int b,int c){
    if(b<1)
        return 1;
    List d=Arrays.asList(b,c);
    if(a.containsKey(d))
        return(int)a.get(d);
    int e,f;
    for(e=f=0;f++<Math.min(c, b);)
        e+=b(b-f,f);
    a.put(d,e);
    return e;
}

Адаптировано с /programming//a/2163753/4230423 и в гольф

Как это работает: мы можем вычислить, сколько разделов целого числа n существует за O ( n 2 ) времени. В качестве побочного эффекта это приводит к таблице размера O ( n 2 ), которую мы затем можем использовать для генерации k- го раздела n для любого целого числа k за время O ( n ).

Итак, пусть total = количество разделов. Выберите случайное число k от 0 до итога - 1. Сгенерируйте k- й раздел.

Как обычно , предложения приветствуются :)

Номер один
источник
1

Python 2, 173 байта

from random import*
N,M=input__
R=67;d=[(0,[])]*R*R
for k in range(R*R):p,P=d[k+~R];q,Q=d[k-k%R*R];d[k]=p+q+0**k,[[x+1 for x in Q],[1]+P][random()*(p+q)<p]
print d[N*R+M][1]

Рекурсивный делает словарь d, с ключами , kпредставляющими собой пару (n,m)пути k=67*n+m( с помощью гарантированного n<=65). Запись является кортежем номера раздела nвm части и случайных таких разбиений. Подсчет вычисляется по рекурсивной формуле (спасибо feersum за указание на это)

f(n,m) = f(n-1,m-1) + f(n,n-m),

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

У меня было много проблем с получением значений за пределами mи nподсчетом нуля. Сначала я использовал словарь со значением по умолчанию 0 и пустым списком. Здесь я использую список и дополняю его этой записью по умолчанию. Отрицательные индексы приводят к тому, что список читается с его конца, что дает запись по умолчанию, которая не близка к концу, достигнутому когда-либо, а обтекания касаются только области, где m>n.

XNOR
источник
1

80386 машинный код, 105 байт

Hexdump кода:

60 8b fa 81 ec 00 41 00 00 33 c0 8b f4 33 d2 42
89 14 06 42 33 ed 8b d8 03 2c 1e 2a fa 73 f9 83
c6 04 89 2c 06 42 3b d1 76 ea fe c4 3a e1 76 db
33 d2 0f c7 f0 f7 f5 86 e9 85 d2 74 1b 33 c0 8d
34 0c 39 14 86 77 03 40 eb f8 2b 54 86 fc 40 89
07 83 c7 04 2a e8 77 e1 42 89 17 83 c7 04 fe cd
7f f7 4a b6 41 03 e2 61 c3

В качестве функции C: void random_partition(int n, int result[]);. Возвращает результат в виде списка чисел в предоставленном буфере; он не помечает конец списка никоим образом, но пользователь может обнаружить конец, накапливая числа - список заканчивается, когда сумма равна n.

Как использовать (в Visual Studio):

#include <stdio.h>

__declspec(naked) void __fastcall random_partiton(int n, int result[])
{
#define a(byte) __asm _emit 0x ## byte
a(60) a(8b) a(fa) a(81) a(ec) a(00) a(41) a(00) a(00) a(33) a(c0) a(8b) a(f4) a(33) a(d2) a(42)
a(89) a(14) a(06) a(42) a(33) a(ed) a(8b) a(d8) a(03) a(2c) a(1e) a(2a) a(fa) a(73) a(f9) a(83)
a(c6) a(04) a(89) a(2c) a(06) a(42) a(3b) a(d1) a(76) a(ea) a(fe) a(c4) a(3a) a(e1) a(76) a(db)
a(33) a(d2) a(0f) a(c7) a(f0) a(f7) a(f5) a(86) a(e9) a(85) a(d2) a(74) a(1b) a(33) a(c0) a(8d)
a(34) a(0c) a(39) a(14) a(86) a(77) a(03) a(40) a(eb) a(f8) a(2b) a(54) a(86) a(fc) a(40) a(89)
a(07) a(83) a(c7) a(04) a(2a) a(e8) a(77) a(e1) a(42) a(89) a(17) a(83) a(c7) a(04) a(fe) a(cd)
a(7f) a(f7) a(4a) a(b6) a(41) a(03) a(e2) a(61) a(c3)
}

void make_stack() // see explanations about stack below
{
    volatile int temp[65 * 64];
    temp[0] = 999;
}

int main()
{
    int result[100], j = 0, n = 64, counter = n;
    make_stack(); // see explanations about stack below

    random_partiton(n, result);

    while (counter > 0)
    {
        printf("%d ", result[j]);
        counter -= result[j];
        ++j;
    }
    putchar('\n');
}

Пример вывода (с n = 64):

21 7 4 4 3 3 3 3 2 2 2 2 2 1 1 1 1 1 1

Это требует много объяснений ...

Конечно, я использовал алгоритм, который использовали все остальные; не было никакого выбора с требованием о сложности. Поэтому мне не нужно слишком много объяснять алгоритм. Тем не мение:

Обозначаю f(n, m)количество разделений nэлементов с использованием частей, не превышающее m. Я храню их в двумерном массиве (объявленном в C как f[65][64]), где первый индекс n, а второй m-1. Я решил, что поддержка n=65была слишком большой проблемой, поэтому отказался от нее ...

Вот код C, который вычисляет эту таблицу:

#define MAX_M 64
int f[(MAX_M + 1) * MAX_M];
int* f2;
int c; // accumulates the numbers needed to calculate f(n, m)
int m;
int k; // f(k, m), for various values of k, are accumulated
int n1;

for (n1 = 0; n1 <= n; ++n1)
{
    f2 = f;
    f2[n1 * MAX_M] = 1;
    for (m = 2; m <= n; ++m)
    {
        c = 0;
        k = n1;
        while (k >= 0)
        {
            c += f2[k * MAX_M];
            k -= m;
        }
        ++f2;
        f2[n1 * MAX_M] = c;
    }
}

Этот код имеет некоторый запутанный стиль, поэтому он может быть легко преобразован в язык ассемблера. Он рассчитывает элементы до f(n, n), который является количеством разбиений nэлементов. Когда этот код завершен, временная переменная cсодержит необходимое число, которое можно использовать для выбора случайного разбиения:

int index = rand() % c;

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

do {
    if (index == 0)
        break;

    m = 0;
    f2 = &f[n * MAX_M];
    while (f2[m] <= index)
    {
        ++m;
    }

    index -= f2[m-1];
    ++m;
    *result++ = m;
    n -= m;
} while (n > 0);

do {
    *result++ = 1;
    --n;
} while (n > 0);

Этот код также оптимизирован для преобразования на язык ассемблера. Есть небольшая «ошибка»: если разделение не содержит 1числа в конце, последний цикл встречается n = 0и выводит ненужный 1элемент. Однако это не повредит, потому что код печати отслеживает сумму числа и не печатает это постороннее число.

При преобразовании во встроенную сборку этот код выглядит следующим образом:

__declspec(naked) void _fastcall random_partition_asm(int n, int result[])
{
    _asm {
        pushad;

        // ecx = n
        // edx = m
        // bh = k; ebx = k * MAX_M * sizeof(int)
        // ah = n1; eax = n1 * MAX_M * sizeof(int)
        // esp = f
        // ebp = c
        // esi = f2
        // edi = result

        mov edi, edx;
        sub esp, (MAX_M + 1) * MAX_M * 4; // allocate space for table
        xor eax, eax;
    row_loop:
        mov esi, esp;
        xor edx, edx;
        inc edx;
        mov dword ptr [esi + eax], edx;
        inc edx;

    col_loop:
        xor ebp, ebp;
        mov ebx, eax;

    sum_loop:
        add ebp, [esi + ebx];
        sub bh, dl;
        jae sum_loop;

        add esi, 4;
        mov [esi + eax], ebp;
        inc edx;
        cmp edx, ecx;
        jbe col_loop;

        inc ah;
        cmp ah, cl;
        jbe row_loop;

        // Done calculating the table

        // ch = n; ecx = n * MAX_M * sizeof(int)
        // eax = m
        // ebx = 
        // edx = index
        // esp = f
        // esi = f2
        // ebp = c
        // edi = result

        xor edx, edx;
        rdrand eax; // generate a random number
        div ebp; // generate a random index in the needed range
        xchg ch, cl; // multiply by 256

    n_loop:
        test edx, edx;
        jz out_trailing;
        xor eax, eax;
        lea esi, [esp + ecx];

    m_loop:
        cmp [esi + eax * 4], edx;
        ja m_loop_done;
        inc eax;
        jmp m_loop;
    m_loop_done:

        sub edx, [esi + eax * 4 - 4];
        inc eax;
        mov [edi], eax;
        add edi, 4;
        sub ch, al;
        ja n_loop;

    out_trailing:
        inc edx;
    out_trailing_loop:
        mov dword ptr [edi], edx;
        add edi, 4;
        dec ch;
        jg out_trailing_loop;

        dec edx;
        mov dh, (MAX_M + 1) * MAX_M * 4 / 256;
        add esp, edx;
        popad;
        ret;
    }
}

Некоторые забавные вещи, чтобы отметить:

  • Генерация случайного числа занимает всего 3 байта машинного кода ( rdrandинструкция)
  • По совпадению размер таблицы равен 64, поэтому размер одной строки составляет 256 байт. Я использую это для хранения индексов строк в «старших» байтах, например ah, что дает мне автоматическое умножение на 256. Чтобы воспользоваться этим, я пожертвовал поддержкой n = 65. Я надеюсь, что я могу быть оправдан за этот грех ...
  • Выделение пространства в стеке осуществляется путем вычитания 0x4100 из регистра указателя стека esp. Это 6-байтовая инструкция! При добавлении этого числа обратно мне удалось сделать это за 5 байтов:

        dec edx; // here edx = 1 from earlier calculations
        mov dh, (MAX_M + 1) * MAX_M * 4 / 256; // now edx = 0x4100
        add esp, edx; // this deallocates space on stack
    
  • При отладке этой функции в MS Visual Studio я обнаружил, что происходит сбой при записи данных в пространство, выделенное для стека! Немного покопавшись, я обнаружил какую-то защиту от переполнения стека: ОС, кажется, выделяет для стека только очень ограниченный диапазон виртуальных адресов; если функция обращается к адресу слишком далеко, ОС предполагает, что это переполнение, и убивает программу. Однако, если функция имеет много локальных переменных, ОС делает некоторую дополнительную «магию», чтобы заставить ее работать. Поэтому я должен вызвать пустую функцию с большим массивом, выделенным в стеке. После возврата этой функции выделяются дополнительные страницы VM стека и их можно использовать.

        void make_stack()
        {
            volatile int temp[65 * 64];
            temp[0] = 999; // have to "use" the array to prevent optimizing it out
        }
    
anatolyg
источник