Golfe Aleatório do Dia # 3: Partições Inteiras


19

Sobre a série

Primeiro, você pode tratar isso como qualquer outro desafio de código de golfe e respondê-lo sem se preocupar com a série. No entanto, existe uma tabela de classificação em todos os desafios. Você pode encontrar a tabela de classificação junto com mais informações sobre a série no primeiro post .

Embora eu tenha várias idéias alinhadas para a série, os desafios futuros ainda não estão definidos. Se você tiver alguma sugestão, informe-me na postagem da sandbox relevante .

Buraco 3: Partições Inteiras

Hora de aumentar um pouco a dificuldade.

Uma partição de um número inteiro positivo né definida como um conjunto múltiplo de números inteiros positivos que somam n. Como exemplo n = 5, se existirem as seguintes partições:

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

Note-se que estes são multisets, portanto, não há fim para eles, {3,1,1}, {1,3,1}e {1,1,3}são todos considerados idênticos.

Sua tarefa é, dada n, gerar uma partição aleatória de n. Aqui estão as regras detalhadas:

  • A distribuição das partições produzidas deve ser uniforme . Ou seja, no exemplo acima, cada partição deve ser retornada com probabilidade 1/7.

    Obviamente, devido às limitações técnicas dos PRNGs, a uniformidade perfeita será impossível. Com o objetivo de avaliar a uniformidade de seu envio, as seguintes operações serão consideradas como produzindo distribuições perfeitamente uniformes:

    • Obtenção de um número de um PRNG (acima de qualquer intervalo), que está documentado para ser (aproximadamente) uniforme.
    • Mapear uma distribuição uniforme sobre um conjunto maior de números para um conjunto menor via módulo ou multiplicação (ou alguma outra operação que distribua valores uniformemente). O conjunto maior deve conter pelo menos 1024 vezes o maior número possível de valores que o conjunto menor.
  • Como as partições são multisets, você pode devolvê-las em qualquer ordem, e essa ordem não precisa ser consistente. No entanto, para fins da distribuição aleatória, a ordem é ignorada. Ou seja, no exemplo acima {3,1,1}, {1,3,1}e {1,1,3} juntos devem ter uma probabilidade de 1/7 de serem retornados.

  • Seu algoritmo deve ter um tempo de execução determinístico. Em particular, você não pode gerar multisets aleatórios e rejeitá-los se não somarem n.
  • A complexidade de tempo do seu algoritmo deve ser polinomial n. Em particular, você não pode simplesmente gerar todas as partições e selecionar uma aleatória (já que o número de partições aumenta exponencialmente n). Você pode supor que o PRNG que você está usando pode retornar valores uniformemente distribuídos em O (1) por valor.
  • Você não deve usar nenhuma função interna que resolva esta tarefa.

Você pode escrever um programa completo ou uma função e obter entrada via STDIN ou alternativa mais próxima, argumento de linha de comando ou argumento de função e produzir saída via valor de retorno ou imprimindo em STDOUT (ou alternativa mais próxima).

Você pode assumir que n ≤ 65(de modo que o número de partições seja menor que 2 21 ). A saída pode estar em qualquer lista conveniente ou inequívoca ou formato de string.

Se você enviar uma função, considere também fornecer um pequeno programa de teste que chame a função várias vezes e imprima os resultados. Tudo bem se os parâmetros tiverem que ser ajustados no código. Isso é apenas para que as pessoas possam verificar se a solução é pelo menos aproximadamente uniforme.

Isso é código de golfe, então a submissão mais curta (em bytes) vence. E, é claro, o menor envio por usuário também entrará na tabela geral de líderes da série.

Entre os melhores

O primeiro post da série gera uma tabela de classificação.

Para garantir que suas respostas sejam exibidas, inicie todas as respostas com um título, usando o seguinte modelo de remarcação:

# Language Name, N bytes

onde Nestá o tamanho do seu envio. Se você melhorar sua pontuação, poderá manter as pontuações antigas no título, identificando-as. Por exemplo:

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

(O idioma não é mostrado no momento, mas o snippet exige e o analisa, e eu posso adicionar um cabeçalho por idioma no futuro.)

Respostas:


8

Python 2, 179 bytes

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

Usei a fórmula (39) deste extrato de Knuth , que fornece o número de partições nque possuem exatamente mpartes. Isso é igual ao número de partições nque possuem mcomo elemento máximo, que é a interpretação que estou usando. Os elementos da partição são gerados do maior para o menor. Em cada estágio, a fórmula é reutilizada com o restante atual ne o elemento máximo permitido.


5

Dyalog APL, 67 59 51 bytes

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

pé um vetor de vetores no qual p[n][k]é o número de partições de nem ksoma, ou equivalente: o número de partições com maior soma k. Construímos pcomeçando com o vetor vazio , lendo n(a entrada de leituras) e aplicando repetidamente o seguinte:

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

Depois de napplications ( ⍣⎕), nós construímos p.

fescolhe uma partição aleatória. n f ké uma partição aleatória de no máximo k summands. 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

Algumas melhorias:

  • inline pao custo de desempenho um pouco pior (mas ainda bom o suficiente)

  • no cálculo de preorganizar e 1,salvar um caractere

  • transformar {1++/(?+/⍵)>+\⍵}em um trem com 1+na frente:1+(+/(?+/)>+\)

  • faça fuma função anônima e forneça (entrada avaliada) como argumento para obter um programa completo

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

Teste com n = 5

Teste com n = 65

E o link a seguir executa n = 5 milhares de vezes e reúne estatísticas sobre a frequência de cada partição: ⎕rl←0 ⋄ {⍺,⍴⍵}⌸ {⍵=0:⍬⋄a,a∇⍵-a←1+(+/(?+/)>+\)⍺↑⍵⊃{⍵,⊂⌽1,+/¨⍵↑¨⍨⌽⍳⍴⍵}⍣⍵⊢⍬}⍨ ¨10000⍴5


Mais melhorias, com a ajuda de Roger Hui :

  • substitua {⍵=0:A⋄B}por {×⍵:B⋄A}. Signum ( ×⍵) retorna verdadeiro para ⍵>0e falso para ⍵=0.

  • substitua (+/(?+/)>+\)por +/b<?⊃⌽b←+\, salva um personagem

  • use uma matriz em vez do vetor de vetores para calcular p: substitua ⍵⊃{⍵,⊂⌽1,+/¨⍵↑¨⍨⌽⍳⍴⍵}⍣⍵⊢⍬por ⊃↓(0,⍨⊢⍪⍨1 1⍉+\)⍣⍵⍪1.

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

teste n = 5 ; teste n = 65 ; estatísticas de frequência


2
Como obter ajuda de Roger Hui?
FUZxxl

5
Escreva um intérprete de APL de brinquedo para ser contratado na mesma empresa que ele. Coloque a expressão acima como um desafio, prometa uma cerveja para cada personagem que ele escolher. Então lucre: menos personagens e mais bebida porque ele não bebe cerveja.
NGN

11
Entendo. Essa é uma estratégia elegante, vamos ver se consigo reproduzir isso ... Você pode perguntar se o Dyalog APL vai receber algo como o J em u/\. ybreve?
FUZxxl


Obrigado por perguntar a ele. Agora me pergunto se é possível em tempo linear também.
FUZxxl

4

GolfScript, 90 bytes

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

Demonstração online

Esta é uma adaptação do meu código de contagem de partições (mais simples) que, em vez de apenas rastrear uma contagem, rastreia uma contagem e um dos elementos contados selecionados uniformemente.

Comparação lado a lado dos dois:

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

Diferenças:

  • A inicial ~é porque este é um programa e não um trecho.
  • A [1.]substituição 1corresponde à mudança no que é rastreado.
  • O adicional {(\{)}%+}%incrementa cada elemento nessa partição e os {1+}%adiciona 1à partição.
  • 0torna-se [0](jogado para 1,) como parte da mudança no que é rastreado, mas, como precisa permanecer uma matriz quando anexada a outra, precisa do extra [ ].
  • A soma simples {+}*se torna uma seleção ponderada das partições, combinada com a soma de sua contagem.
  • O (;`remove a contagem da saída e coloca a partição em um bom formato.

Estrutura de teste

;7000,{;
  '5'

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

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

Ajuste o 7000 inicial se você quiser executar um número diferente de tentativas. Observe que isso é muito lento para uma demonstração online.


3

Java, 285 267 bytes

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;}

Esse é o mesmo método da resposta do TheBestOne, mas usa uma matriz simples em vez de um mapa. Além disso, em vez de retornar a partição aleatória como a List, ela as imprime no console.

Abaixo está um programa de teste que o executa 100000 vezes. Por exemplo n=5, todos os conjuntos estavam dentro de 0,64% de um 1/7 perfeito na minha última execução.

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
Embora você tenha golfed a Math.minchamada para baixo para (k<n?k:n), você pode ir mais longe, deixando de lado-a inteiramente e apenas fazendo duas verificações: b<k&b++<n. Você também pode abandonar facilmente a n>0parte do loop condicional (já que n>0&b<nreduz a b<nquando bé garantido não negativo).
Peter Taylor

@PeterTaylor Thanks. Dando uma outra olhada, deixe-me livrar da declaração de retorno extra e da intdeclaração em separado também.
Geobits

3

CJam, 64 56 bytes

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

Você pode testá-lo com este script:

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

Explicação

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
Você deve remover o "não golfed muito bem" parte incorreta da sua resposta;)
anatolyg

@anatolyg Removido. Mas acredito que ainda é possível remover alguns bytes. Estou com preguiça de fazer isso.
jimmy23013

3

Pitão, 64 bytes

Usa /programming//a/2163753/4230423, exceto que a) Nenhum cache desde que Pyth memoriza automaticamente, b) Imprime cada um em vez de anexar à lista ec) é traduzido para Pyth.

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

Vou postar uma explicação disso quando tiver tempo, mas aqui está o código python correspondente:

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

Edit: Eu finalmente comecei a fazer a explicação:

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

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

Construa uma matriz quadrada em que cada célula (m, n) reflita o número de partições mcujo maior número é n, de acordo com o extrato de Knuth @feersum, tão gentilmente citado. Por exemplo, 5,2nos dá 2 porque existem duas partições válidas 2,2,1e 2,1,1,1. 6,3nos dá 3 para 3,1,1,1, 3,2,1e 3,3.

Agora podemos encontrar deterministicamente a partição p'th. Aqui, estamos gerando pcomo um número aleatório, mas você pode alterar um pouco o script, assim pcomo um parâmetro:

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

Agora podemos mostrar deterministicamente que cada resultado depende exclusivamente de 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

Assim, voltando ao original onde p é gerado aleatoriamente, podemos ter certeza de que cada resultado é igualmente provável.


Não tenho certeza do seu exemplo 5,2. As duas partições não deveriam ser (2,2,1)e (2,1,1,1,1)(já que as duas que você listou têm números maiores que 2).
Martin Ender

Você está certo, eu tenho coisas distorcidas. Existem duas partições com dois componentes e duas partições começando com 2. Eu quis dizer o último.
dcsohl 24/02

2

R, 198 bytes

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

Ele segue a mesma estrutura da ótima solução do @ dcsohl no Octave e, portanto, também é baseado no extrato de Knuth publicado pelo @feersum.

Editarei isso mais tarde, se puder encontrar uma solução mais criativa em R. Enquanto isso, qualquer entrada é bem-vinda.


1

Java, 392 bytes

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;}

Ligue com a(n). Retorna um Listde Integers

Recuado:

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;
}

Adaptado de /programming//a/2163753/4230423 e golfed

Como isso funciona: Podemos calcular quantas partições de um número inteiro n existem no tempo O ( n 2 ). Como efeito colateral, isso produz uma tabela de tamanho O ( n 2 ) que podemos usar para gerar a k- ésima partição de n , para qualquer número inteiro k , em O ( n ) tempo.

Então vamos total = o número de partições. Escolha um número aleatório k de 0 ao total - 1. Gere a k- ésima partição.

Como sempre , sugestões são bem-vindas :)


1

Python 2, 173 bytes

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]

Recursivamente faz um dicionário d, com as teclas kque representam um par (n,m)por k=67*n+m(usando o garantido n<=65). A entrada é o tuplo do número de partição de nem mpartes, e uma tal partição aleatória. As contagens são calculadas pela fórmula recursiva (obrigado ao feersum por indicá-lo)

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

e a partição aleatória é atualizada escolhendo um dos dois ramos com probabilidade proporcional à sua contagem. A atualização é feita adicionando-se anexando a 1para o primeiro ramo e incrementando cada elemento para o segundo.

Eu tive muitos problemas para obter valores fora dos limites me ndar contagens zero. No começo, usei um dicionário que possui como padrão uma contagem de 0 e uma lista vazia. Aqui, estou usando uma lista e preenchendo-a com esta entrada padrão. Índices negativos fazem com que a lista seja lida a partir do final, o que fornece uma entrada padrão que nunca chegou ao fim como nunca alcançada, e os envolvimentos tocam apenas uma região em que m>n.


1

Código da máquina 80386, 105 bytes

Hexdump do código:

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

Como uma função de C: void random_partition(int n, int result[]);. Retorna o resultado como uma lista de números no buffer fornecido; não marca o fim da lista de forma alguma, mas o usuário pode descobrir o final acumulando os números - a lista termina quando a soma é igual a n.

Como usar (no 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');
}

Exemplo de saída (com n = 64):

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

Isso requer muitas explicações ...

Claro que usei o algoritmo que todo mundo também usou; não havia escolha com o requisito sobre complexidade. Portanto, não preciso explicar muito o algoritmo. De qualquer forma:

Eu denoto pelo f(n, m)número de particionamentos de nelementos usando partes não maiores que m. Eu os armazeno em uma matriz 2-D (declarada em C como f[65][64]), onde está o primeiro índice ne o segundo m-1. Eu decidi que apoiar n=65era demais, então o abandonei ...

Aqui está o código C que calcula esta tabela:

#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;
    }
}

Esse código possui um estilo ofuscado, portanto pode ser convertido para linguagem assembly facilmente. Ele calcula os elementos até f(n, n), que é o número de particionamentos de nelementos. Quando esse código é concluído, a variável temporária ccontém o número necessário, que pode ser usado para selecionar um particionamento aleatório:

int index = rand() % c;

Posteriormente, isso indexé convertido no formato necessário (lista de números) usando a tabela gerada.

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

Este código também é otimizado para conversão em linguagem assembly. Há um pequeno "bug": se o particionamento não contém nenhum 1número no final, o último loop encontra n = 0e gera um 1elemento desnecessário . No entanto, não dói, porque o código de impressão rastreia a soma do número e não imprime esse número estranho.

Quando convertido em assembly embutido, esse código se parece com o seguinte:

__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;
    }
}

Algumas coisas divertidas a serem observadas:

  • A geração de um número aleatório requer apenas 3 bytes de código de máquina ( rdrandinstrução)
  • Por coincidência, o tamanho da tabela é 64, portanto, o tamanho de uma linha é de 256 bytes. Eu uso isso para armazenar índices de linha em registros de "byte alto" ah, como , o que me dá multiplicação automática por 256. Para tirar vantagem disso, sacrifiquei o suporte n = 65. Espero poder me desculpar por esse pecado ...
  • A alocação de espaço na pilha é realizada subtraindo 0x4100 do registro do ponteiro da pilha esp. Esta é uma instrução de 6 bytes! Ao adicionar esse número de volta, consegui fazê-lo em 5 bytes:

        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
    
  • Ao depurar essa função no MS Visual Studio, descobri que ela falha quando grava dados no espaço alocado na pilha! Após algumas pesquisas, descobri algum tipo de proteção contra sobrecarga de pilha: o sistema operacional parece alocar apenas um intervalo muito limitado de endereços virtuais para pilha; se uma função acessa um endereço muito distante, o SO assume que é uma saturação e mata o programa. No entanto, se uma função tiver muitas variáveis ​​locais, o SO fará alguma "mágica" extra para fazê-la funcionar. Então, eu tenho que chamar uma função vazia que tem uma grande matriz alocada na pilha. Depois que essa função retorna, páginas adicionais da VM da pilha são alocadas e podem ser usadas.

        void make_stack()
        {
            volatile int temp[65 * 64];
            temp[0] = 999; // have to "use" the array to prevent optimizing it out
        }
    
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.