Випадковий гольф дня №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(таким чином, що кількість розділів менше 2 21 ). Вихід може бути у будь-якому зручному, недвозначному списку чи рядковому форматі.

Якщо ви подаєте функцію, будь ласка, також слід передбачити невелику програму тестування, яка кілька разів викликає функцію та друкує результати. Це нормально, якщо параметри потрібно ввести в код. Це просто для того, щоб люди могли перевірити, що рішення є принаймні приблизно рівномірним.

Це код гольфу, тому виграє найкоротше подання (у байтах). І звичайно, найкоротше подання на кожного користувача також увійде в загальну таблицю лідерів серії.

Таблиця лідерів

Перший пост серії генерує таблицю лідерів.

Щоб відповіді відображалися, почніть кожну відповідь із заголовка, використовуючи такий шаблон Markdown:

# Language Name, N bytes

де Nрозмір вашого подання. Якщо ви покращите свій рахунок, ви можете зберегти старі бали у заголовку, прокресливши їх. Наприклад:

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

(Мова наразі не відображається, але фрагмент вимагає і аналізує його, і я можу в майбутньому додати таблицю лідерів за мовою.)

Відповіді:


8

Пітон 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та максимально дозволеним елементом.


5

Діялог APL, 67 59 51 байт

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

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

{⍵,⊂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

Деякі вдосконалення:

  • inline 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 ; freq статистика


2
Як можна отримати допомогу від Роджера Хуя?
FUZxxl

5
Напишіть іграшкового перекладача APL, щоб прийняти на роботу в тій же компанії, що і його. Поставте вищенаведений вираз як виклик, пообіцяйте пінту пива кожному герою, який він виймає. Тоді прибуток: менше персонажів і більше випивки, оскільки він не п'є пиво.
ngn

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,) як частина зміни в тому, що відслідковується, але оскільки він повинен залишатися масивом, коли він виглядає перед іншим, йому потрібна додаткова [ ].
  • Проста сума {+}*стає зваженим виділенням з розділів у поєднанні з підрахунком їх кількості.
  • (;`Знімає відлік від виходу і розміщує розділ у форматі приємно.

Тестові рамки

;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, але він використовує простий масив замість карти. Крім того, замість повернення випадкового розділу як a 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;
    }

}

3
Хоча ви golfed на Math.minвиклик аж до (k<n?k:n), ви можете піти далі, канав його повністю і просто робити дві перевірки: b<k&b++<n. Ви також можете легко скинути n>0частину циклу умовно (оскільки n>0&b<nзводиться до того, b<nколи bгарантовано негативно).
Пітер Тейлор

@PeterTaylor Дякую З іншого боку, я також позбудусь додаткової заяви про повернення та окремої intдекларації.
Геобіт

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. ";

2
Ви повинні видалити неправильну частину вашої відповіді "не гольфувати дуже добре";)
anatolyg

@anatolyg Видалено. Але я вважаю, що все-таки можливо видалити деякі байти. Мені просто лінь це робити.
jimmy23013

3

Pyth, 64 байти

Використовує /programming//a/2163753/4230423 за винятком того, що: a) немає кешу, оскільки 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

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

Безголівки:

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 @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це параметр:

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

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


Я не впевнений у вашому прикладі 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}

Безголівки:

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 , і, таким чином, також заснований на витязі Knuth витязі опублікованому @feersum.

Я відредагую це пізніше, якщо зможу знайти більш креативне рішення в Р. Тим часом будь-який вклад, звичайно, вітається.


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),

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

У мене було багато проблем із виведенням значень поза межі mта nпідрахунком нуля. Спочатку я використав словник, який за замовчуванням дорівнює 0 та порожній список. Тут я використовую список і замість цього додаю цей запис за замовчуванням. Негативні індекси спричиняють зчитування списку з його кінця, що дає запис за замовчуванням - це ніщо не до кінця, як колись досягнуте, а обгортки лише торкаються регіону, де m>n.


1

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

Шестнадцятковий код:

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
        }
    
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.