Golf casuale del giorno # 3: Partizioni intere


19

Informazioni sulla serie

Prima di tutto, puoi trattarlo come qualsiasi altra sfida di golf del codice e rispondere senza preoccuparti della serie. Tuttavia, esiste una classifica in tutte le sfide. Puoi trovare la classifica insieme ad alcune ulteriori informazioni sulla serie nel primo post .

Anche se ho un sacco di idee in programma per la serie, le sfide future non sono ancora state messe sulla pietra. Se hai qualche suggerimento, per favore fatemelo sapere sul post sandbox pertinente .

Hole 3: Partizioni intere

È ora di aumentare un po 'la difficoltà.

Una partizione di un numero intero positivo nè definita come un multiset di numeri interi positivi che si sommano a n. Ad esempio se n = 5, esistono le seguenti partizioni:

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

Si noti che questi sono multiinsiemi, quindi non c'è per loro {3,1,1}, {1,3,1}e {1,1,3}sono tutti considerati identici.

Il tuo compito è, dato n, generare una partizione casuale di n. Ecco le regole dettagliate:

  • La distribuzione delle partizioni prodotte deve essere uniforme . Cioè, nell'esempio sopra, ogni partizione dovrebbe essere restituita con probabilità 1/7.

    Naturalmente, a causa delle limitazioni tecniche dei PRNG, la perfetta uniformità sarà impossibile. Ai fini della valutazione dell'uniformità della presentazione, le seguenti operazioni saranno considerate come una distribuzione perfettamente uniforme:

    • Ottenere un numero da un PRNG (su qualsiasi intervallo), che è documentato come (approssimativamente) uniforme.
    • Mappare una distribuzione uniforme su un set di numeri più grande su un set più piccolo tramite modulo o moltiplicazione (o qualche altra operazione che distribuisce i valori in modo uniforme). Il set più grande deve contenere almeno 1024 volte il maggior numero possibile di valori del set più piccolo.
  • Poiché le partizioni sono multiset, è possibile restituirle in qualsiasi ordine e questo ordine non deve essere coerente. Tuttavia, ai fini della distribuzione casuale, l'ordine viene ignorato. Cioè, nell'esempio sopra {3,1,1}, {1,3,1}e {1,1,3} insieme devono avere una probabilità di 1/7 di essere restituiti.

  • L'algoritmo deve avere un runtime deterministico. In particolare, non è possibile generare multiset casuali e rifiutarli se non si sommano n.
  • La complessità temporale dell'algoritmo deve essere polinomiale n. In particolare, non è possibile semplicemente generare tutte le partizioni e selezionarne una casuale (poiché il numero di partizioni aumenta esponenzialmente con n). Si può presumere che il PRNG in uso possa restituire valori distribuiti uniformemente in O (1) per valore.
  • Non è necessario utilizzare alcuna funzione integrata che risolva questa attività.

È possibile scrivere un programma completo o una funzione e accettare input tramite STDIN o alternativa più vicina, argomento della riga di comando o argomento della funzione e produrre output tramite valore di ritorno o stampando su STDOUT (o alternativa più vicina).

Si può presumere che n ≤ 65(tale che il numero di partizioni sia inferiore a 2 21 ). L'output può essere in qualsiasi elenco o formato stringa comodo e inequivocabile.

Se invii una funzione, ti preghiamo di considerare di fornire anche un piccolo programma di test che chiama la funzione più volte e stampa i risultati. Va bene se i parametri devono essere modificati nel codice. Questo è solo così le persone possono verificare che la soluzione sia almeno approssimativamente uniforme.

Questo è il golf del codice, quindi vince l'invio più breve (in byte). E, naturalmente, la presentazione più breve per utente entrerà anche nella classifica generale della serie.

Classifica

Il primo post della serie genera una classifica.

Per assicurarti che le tue risposte vengano visualizzate, inizia ogni risposta con un titolo, utilizzando il seguente modello Markdown:

# Language Name, N bytes

dov'è Nla dimensione del tuo invio. Se si migliora il punteggio, è possibile mantenere i vecchi punteggi nel titolo, colpendoli. Per esempio:

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

(La lingua non è attualmente visualizzata, ma lo snippet richiede e analizza, e in futuro potrei aggiungere una classifica linguistica).

Risposte:


8

Python 2, 179 byte

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

Ho usato la formula (39) da questo estratto di Knuth , che fornisce il numero di partizioni nche hanno esattamente mparti. Questo accade per eguagliare il numero di partizioni di nquello che ha mcome elemento massimo, che è l'interpretazione che sto usando. Gli elementi della partizione sono generati dal più grande al meno. In ogni fase la formula viene riutilizzata con il resto corrente ne l'elemento massimo consentito.


5

Dyalog APL, 67 59 51 byte

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

pè un vettore di vettori in cui p[n][k]è il numero di partizioni di nin kvertici, o equivalentemente: il numero di partizioni con il maggior summand k. Costruiamo piniziando con il vettore vuoto , leggendo n(l' input delle letture) e applicando ripetutamente quanto segue:

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

Dopo le napplicazioni ( ⍣⎕), abbiamo creato p.

fseleziona una partizione casuale. n f kè una partizione casuale di al massimo k riassunti. f nlo è 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

Alcuni miglioramenti:

  • in linea pal costo di prestazioni leggermente peggiori (ma comunque abbastanza buone)

  • nel calcolo di priorganizzare e 1,salvare un personaggio

  • trasformarsi {1++/(?+/⍵)>+\⍵}in un treno con 1+davanti:1+(+/(?+/)>+\)

  • eseguire funa funzione anonima e fornire (input valutato) come argomento per ottenere un programma completo

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

Prova con n = 5

Prova con n = 65

E il seguente link esegue n = 5 migliaia di volte e raccoglie statistiche sulla frequenza di ogni partizione: ⎕rl←0 ⋄ {⍺,⍴⍵}⌸ {⍵=0:⍬⋄a,a∇⍵-a←1+(+/(?+/)>+\)⍺↑⍵⊃{⍵,⊂⌽1,+/¨⍵↑¨⍨⌽⍳⍴⍵}⍣⍵⊢⍬}⍨ ¨10000⍴5


Altri miglioramenti, con l'aiuto di Roger Hui :

  • sostituire {⍵=0:A⋄B}con {×⍵:B⋄A}. Signum ( ×⍵) restituisce true per ⍵>0e false per ⍵=0.

  • sostituirlo (+/(?+/)>+\)con +/b<?⊃⌽b←+\, salva un personaggio

  • usa una matrice invece del vettore dei vettori per calcolare p: sostituisci ⍵⊃{⍵,⊂⌽1,+/¨⍵↑¨⍨⌽⍳⍴⍵}⍣⍵⊢⍬con ⊃↓(0,⍨⊢⍪⍨1 1⍉+\)⍣⍵⍪1.

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

test n = 5 ; test n = 65 ; statistiche freq


2
Come si ottiene aiuto da Roger Hui?
FUZxxl

5
Scrivi un interprete APL giocattolo per farti assumere nella stessa compagnia di lui. Metti l'espressione sopra come una sfida, prometti una pinta di birra per ogni personaggio che prende. Quindi profitto: meno personaggi e più alcol perché non beve birra.
ngn

1
Vedo. È una strategia accurata, vediamo se riesco a riprodurlo ... Puoi chiedergli se Dyalog APL otterrà presto qualcosa come J u/\. y?
FUZxxl


Grazie per averlo chiesto. Ora mi chiedo se sia possibile anche nel tempo lineare.
FUZxxl

4

GolfScript, 90 byte

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

Demo online

Questo è un adattamento del mio (più semplice) codice di conteggio delle partizioni che invece di semplicemente tenere traccia di un conteggio tiene traccia sia di un conteggio che di uno selezionato in modo uniforme tra gli elementi conteggiati.

Confronto fianco a fianco dei due:

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

differenze:

  • L'iniziale ~è perché si tratta di un programma piuttosto che di uno snippet.
  • La [1.]sostituzione 1corrisponde alla modifica di ciò che viene tracciato.
  • L'ulteriore {(\{)}%+}%incrementa ogni elemento in quella partizione e l' {1+}%aggiunta 1alla partizione.
  • 0diventa [0](golfed 1,) come parte del cambiamento in ciò che viene tracciato, ma poiché deve rimanere un array quando viene anteposto a un altro, ha bisogno di un extra [ ].
  • La somma semplice {+}*diventa una selezione ponderata dalle partizioni, combinata con una somma del loro conteggio.
  • Il (;`rimuove il conteggio dall'uscita e mette la partizione in un formato piacevole.

Quadro di prova

;7000,{;
  '5'

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

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

Modificare il 7000 iniziale se si desidera eseguire un numero diverso di prove. Nota che questo è troppo lento per una demo online.


3

Java, 285 267 byte

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

Questo è lo stesso metodo della risposta di TheBestOne, ma utilizza un array semplice anziché una mappa. Inoltre, invece di restituire la partizione casuale come a List, le stampa sulla console.

Di seguito è riportato un programma di test che lo esegue 100000 volte. Nell'esempio n=5, tutti i set erano entro lo 0,64% di un 1/7 perfetto nella mia ultima corsa.

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
Anche se hai golfed la Math.minchiamata verso il basso per (k<n?k:n), si può andare oltre, ammaraggio del tutto e solo facendo due controlli: b<k&b++<n. Puoi anche abbandonare facilmente la n>0parte del loop condizionale (poiché si n>0&b<nriduce a b<nquando bè garantito non negativo).
Peter Taylor,

@PeterTaylor Grazie. Dando un'altra occhiata, mi permetto di eliminare la dichiarazione di reso supplementare e anche la intdichiarazione separata .
Geobits,

3

CJam, 64 56 byte

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

Puoi provarlo con questo script:

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

Spiegazione

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
Dovresti rimuovere la parte errata della frase "non giocata molto bene";)
anatolyg

@anatolyg Rimosso. Ma credo che sia ancora possibile rimuovere alcuni byte. Sono troppo pigro per farlo.
jimmy23013,

3

Pyth, 64 byte

Utilizza /programming//a/2163753/4230423 eccetto che a) Nessuna cache poiché Pyth memorizza automaticamente le memorie, b) Stampa ciascuna invece di aggiungere all'elenco e c) viene tradotta in Pyth.

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

Pubblicherò una spiegazione di questo quando avrò il tempo, ma ecco il codice python corrispondente:

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

Modifica: finalmente sono riuscito a fare la spiegazione:

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

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

Costruisci una matrice quadrata in cui ogni cella (m, n) riflette il numero di partizioni il mcui numero più grande èn , secondo l'estratto di Knuth @feersum così gentilmente citato. Ad esempio, 5,2ci dà 2 perché ci sono due partizioni valide 2,2,1e 2,1,1,1. 6,3ci dà 3 per 3,1,1,1, 3,2,1e 3,3.

Ora possiamo determinare in modo deterministico l'ennesima partizione. Qui, stiamo generando pcome un numero casuale ma è possibile modificare leggermente lo script, quindi pè un parametro:

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

Ora possiamo determinare in modo deterministico che ogni risultato dipende esclusivamente da 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

Quindi, tornando all'originale in cui p viene generato casualmente, possiamo essere certi che ogni risultato è ugualmente probabile.


Non sono sicuro del tuo esempio 5,2. Le due partizioni non dovrebbero essere (2,2,1)e (2,1,1,1,1)(poiché le due che hai elencato hanno numeri maggiori di 2).
Martin Ender,

Hai ragione, ho le cose contorte. Esistono due partizioni con due componenti e due partizioni che iniziano con 2. Intendevo quest'ultimo.
dcsohl,

2

R, 198 byte

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

Segue la stessa struttura della grande soluzione di @ dcsohl in Octave e si basa quindi anche sull'estratto di Knuth pubblicato da @feersum.

Lo modificherò più tardi se riesco a trovare una soluzione più creativa in R. Nel frattempo, qualsiasi input è ovviamente il benvenuto.


1

Java, 392 byte

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

Chiama con a(n). Restituisce aList di Integers

rientrato:

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

Adattato da /programming//a/2163753/4230423 e giocato a golf

Come funziona: Siamo in grado di calcolare quante partizioni di un intero n ci troviamo in O ( n 2 ) tempo. Come effetto collaterale, questo produce una tabella di dimensioni O ( n 2 ) che possiamo quindi usare per generare la k esima partizione di n , per qualsiasi intero k , nel tempo O ( n ).

Quindi lasciate total = il numero di partizioni. Scegli un numero casuale k da 0 al totale - 1. Genera la k partizione.

Come al solito , i suggerimenti sono benvenuti :)


1

Python 2, 173 byte

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]

Ricorsivamente rende un dizionario d, con tasti kche rappresenta una coppia (n,m)da k=67*n+m(usando l'garantita n<=65). La voce è la tupla del numero di partizioni di ninm parti e una tale partizione casuale. I conteggi sono calcolati dalla formula ricorsiva (grazie al feersum per averlo sottolineato)

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

e la partizione casuale viene aggiornata selezionando uno dei due suoi rami con probabilità proporzionale al suo conteggio. L'aggiornamento viene eseguito aggiungendo viene eseguito aggiungendo a 1per il primo ramo e incrementando ogni elemento per il secondo.

Ho avuto molti problemi a ottenere valori fuori limite me na contare zero. Inizialmente, ho usato un dizionario predefinito con un conteggio di 0 e un elenco vuoto. Qui, sto usando un elenco e lo riempio con questa voce predefinita. Gli indici negativi fanno sì che l'elenco venga letto dalla sua fine, il che fornisce una voce predefinita non è nulla alla fine come mai raggiunta, e gli avvolgenti toccano solo una regione in cui m>n.


1

80386 codice macchina, 105 byte

Hexdump del codice:

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

Come una funzione C: void random_partition(int n, int result[]);. Restituisce il risultato come un elenco di numeri nel buffer fornito; non segna in alcun modo la fine dell'elenco, ma l'utente può scoprire la fine accumulando i numeri: l'elenco termina quando la somma è uguale a n.

Come usare (in 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');
}

Esempio di output (con n = 64):

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

Ciò richiede molte spiegazioni ...

Ovviamente ho usato l'algoritmo usato anche da tutti gli altri; non c'era scelta con il requisito della complessità. Quindi non devo spiegare troppo l'algoritmo. Comunque:

Indico per f(n, m)il numero di partizioni di nelementi usando parti non superiori a m. Le memorizzo in un array 2-D (dichiarato in C come f[65][64]), dove si trova il primo indice ne il secondom-1 . Ho deciso che sostenere n=65era troppo disturbo, quindi l'ho abbandonato ...

Ecco il codice C che calcola questa tabella:

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

Questo codice ha uno stile offuscato, quindi può essere facilmente convertito in linguaggio assembly. Calcola gli elementi fino a f(n, n), che è il numero di partizioni di nelementi. Al termine di questo codice, la variabile temporanea ccontiene il numero necessario, che può essere utilizzato per selezionare un partizionamento casuale:

int index = rand() % c;

Successivamente, questo indexviene convertito nel formato richiesto (elenco di numeri) utilizzando la tabella generata.

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

Questo codice è inoltre ottimizzato per la conversione in linguaggio assembly. C'è un piccolo "bug": se il partizionamento non contiene alcun 1numero alla fine, l'ultimo loop si incontra n = 0e genera un 1elemento non necessario . Non fa male, tuttavia, perché il codice di stampa tiene traccia della somma del numero e non stampa questo numero estraneo.

Quando convertito in assembly inline, questo codice è simile al seguente:

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

Alcune cose divertenti da notare:

  • La generazione di un numero casuale richiede solo 3 byte di codice macchina ( rdrandistruzioni)
  • Per una coincidenza, la dimensione della tabella è 64, quindi la dimensione di una riga è di 256 byte. Uso questo per contenere indici di riga in registri "high-byte" come ah, il che mi dà la moltiplicazione automatica per 256. Per trarne vantaggio, ho sacrificato il supporto per n = 65. Spero di poter essere scusato per questo peccato ...
  • L'allocazione dello spazio sullo stack viene eseguita sottraendo 0x4100 dal registro del puntatore dello stack esp. Questa è un'istruzione a 6 byte! Quando ho aggiunto questo numero, sono riuscito a farlo in 5 byte:

        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
    
  • Durante il debug di questa funzione in MS Visual Studio, ho scoperto che si arresta in modo anomalo quando scrive i dati nello spazio allocato nello stack! Dopo un po 'di ricerche, ho scoperto una sorta di protezione da sovraccarico dello stack: il sistema operativo sembra allocare solo una gamma molto limitata di indirizzi virtuali per lo stack; se una funzione accede a un indirizzo troppo lontano, il sistema operativo presume che sia un sovraccarico e uccide il programma. Tuttavia, se una funzione ha molte variabili locali, il sistema operativo fa qualche "magia" in più per farlo funzionare. Quindi devo chiamare una funzione vuota che ha una grande matrice allocata nello stack. Dopo il ritorno di questa funzione, vengono allocate pagine VM dello stack extra che possono essere utilizzate.

        void make_stack()
        {
            volatile int temp[65 * 64];
            temp[0] = 999; // have to "use" the array to prevent optimizing it out
        }
    
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.