Golf aleatorio del día # 3: particiones enteras


19

Sobre la serie

En primer lugar, puede tratar esto como cualquier otro desafío de golf de código y responderlo sin preocuparse por la serie. Sin embargo, hay una tabla de clasificación en todos los desafíos. Puede encontrar la tabla de clasificación junto con más información sobre la serie en la primera publicación .

Aunque tengo un montón de ideas para la serie, los desafíos futuros aún no están establecidos en piedra. Si tiene alguna sugerencia, hágamelo saber en la publicación de sandbox relevante .

Hoyo 3: Particiones enteras

Es hora de aumentar un poco la dificultad.

Una partición de un entero positivo nse define como un conjunto múltiple de enteros positivos que suman n. Como ejemplo si n = 5, existen las siguientes particiones:

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

Tenga en cuenta que estos son conjuntos múltiples, por lo que no hay un orden para ellos {3,1,1}, {1,3,1}y {1,1,3}todos se consideran idénticos.

Su tarea es, dada n, generar una partición aleatoria de n. Aquí están las reglas detalladas:

  • La distribución de las particiones producidas debe ser uniforme . Es decir, en el ejemplo anterior, cada partición debe devolverse con probabilidad 1/7.

    Por supuesto, debido a las limitaciones técnicas de los PRNG, será imposible una uniformidad perfecta. Para evaluar la uniformidad de su envío, se considerará que las siguientes operaciones producen distribuciones perfectamente uniformes:

    • Obtener un número de un PRNG (sobre cualquier rango), que está documentado como (aproximadamente) uniforme.
    • Mapear una distribución uniforme sobre un conjunto más grande de números en un conjunto más pequeño mediante módulo o multiplicación (o alguna otra operación que distribuya valores de manera uniforme). El conjunto más grande tiene que contener al menos 1024 veces tantos valores posibles como el conjunto más pequeño.
  • Dado que las particiones son multisets, puede devolverlas en cualquier orden, y este orden no tiene que ser coherente. Sin embargo, para el propósito de la distribución aleatoria, el orden se ignora. Es decir, en el ejemplo anterior {3,1,1}, {1,3,1}y {1,1,3} juntos deben tener una probabilidad de 1/7 de ser devueltos.

  • Su algoritmo debe tener un tiempo de ejecución determinista. En particular, no puede generar múltiples conjuntos aleatorios y rechazarlos si no suman n.
  • La complejidad del tiempo de su algoritmo debe ser polinómica n. En particular, no puede simplemente generar todas las particiones y seleccionar una aleatoria (ya que el número de particiones crece exponencialmente con n). Puede suponer que el PRNG que está utilizando puede devolver valores distribuidos uniformemente en O (1) por valor.
  • No debe utilizar ninguna función integrada que resuelva esta tarea.

Puede escribir un programa completo o una función y tomar la entrada a través de STDIN o la alternativa más cercana, argumento de línea de comando o argumento de función y producir salida a través del valor de retorno o imprimiendo en STDOUT (o la alternativa más cercana).

Puede suponer eso n ≤ 65(de modo que el número de particiones sea menor que 2 21 ). La salida puede estar en cualquier formato de lista o cadena conveniente y sin ambigüedades.

Si envía una función, considere también proporcionar un pequeño programa de prueba que llame a la función varias veces e imprima los resultados. Está bien si los parámetros tienen que ser modificados en el código. Esto es solo para que la gente pueda verificar que la solución sea al menos aproximadamente uniforme.

Este es el código de golf, por lo que gana el envío más corto (en bytes). Y, por supuesto, la presentación más corta por usuario también entrará en la tabla de clasificación general de la serie.

Tabla de clasificación

La primera publicación de la serie genera una tabla de clasificación.

Para asegurarse de que sus respuestas aparezcan, comience cada respuesta con un título, utilizando la siguiente plantilla de Markdown:

# Language Name, N bytes

¿Dónde Nestá el tamaño de su envío? Si mejora su puntuación, se puede mantener viejas cuentas en el título, golpeándolos a través. Por ejemplo:

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

(El idioma no se muestra actualmente, pero el fragmento requiere y analiza, y puedo agregar una tabla de clasificación por idioma en el futuro).

Respuestas:


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

Usé la fórmula (39) de este extracto de Knuth , que da el número de particiones nque tienen exactamente mpartes. Esto sucede para igualar el número de particiones nque tiene mcomo elemento máximo, que es la interpretación que estoy usando. Los elementos de la partición se generan de mayor a menor. En cada etapa, la fórmula se reutiliza con el resto actual ny el elemento máximo permitido.


5

Dyalog APL, 67 59 51 bytes

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

pes un vector de vectores en el que se p[n][k]encuentra el número de particiones nen ksumando, o equivalente: el número de particiones con mayor sumando k. Construimos pcomenzando con el vector vacío , leyendo n(la entrada de lecturas) y aplicando repetidamente lo siguiente:

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

Después de las naplicaciones ( ⍣⎕), hemos construido p.

felige una partición aleatoria. n f kes una partición aleatoria de como máximo k sumandos. f nes 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

Algunas mejoras

  • en línea pa costa de un rendimiento ligeramente peor (pero aún lo suficientemente bueno)

  • en el cálculo de preorganizar y 1,guardar un personaje

  • convertirse {1++/(?+/⍵)>+\⍵}en un tren con 1+frente:1+(+/(?+/)>+\)

  • hacer funa función anónima y suministrar (entrada evaluada) como argumento para obtener un programa completo

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

Prueba con n = 5

Prueba con n = 65

Y el siguiente enlace se ejecuta n = 5 miles de veces y recopila estadísticas sobre la frecuencia de cada partición: ⎕rl←0 ⋄ {⍺,⍴⍵}⌸ {⍵=0:⍬⋄a,a∇⍵-a←1+(+/(?+/)>+\)⍺↑⍵⊃{⍵,⊂⌽1,+/¨⍵↑¨⍨⌽⍳⍴⍵}⍣⍵⊢⍬}⍨ ¨10000⍴5


Más mejoras, con la ayuda de Roger Hui :

  • reemplazar {⍵=0:A⋄B}con {×⍵:B⋄A}. Signum ( ×⍵) devuelve verdadero para ⍵>0y falso para ⍵=0.

  • reemplazar (+/(?+/)>+\)con +/b<?⊃⌽b←+\, guarda un personaje

  • use una matriz en lugar del vector de vectores para calcular p: reemplace ⍵⊃{⍵,⊂⌽1,+/¨⍵↑¨⍨⌽⍳⍴⍵}⍣⍵⊢⍬con ⊃↓(0,⍨⊢⍪⍨1 1⍉+\)⍣⍵⍪1.

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

prueba n = 5 ; prueba n = 65 ; estadísticas de freq


2
¿Cómo se obtiene ayuda de Roger Hui?
FUZxxl

55
Escriba un intérprete de APL de juguete para que lo contraten en la misma compañía que él. Plantea la expresión anterior como un desafío, promete una pinta de cerveza por cada personaje que saque. Luego se beneficia: menos personajes y más alcohol ya que no bebe cerveza.
ngn

1
Veo. Esa es una buena estrategia, veamos si puedo reproducir eso ... ¿Puedes preguntarle si Dyalog APL va a obtener algo como J en el corto u/\. yplazo?
FUZxxl


Gracias por preguntarle Ahora me pregunto si también es posible en tiempo lineal.
FUZxxl

4

GolfScript, 90 bytes

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

Demostración en línea

Esta es una adaptación de mi código de conteo de partición (más simple) que en lugar de simplemente rastrear un conteo rastrea tanto un conteo como uno de los elementos contados seleccionados uniformemente.

Comparación lado a lado de los dos:

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

Diferencias:

  • La inicial ~es porque este es un programa en lugar de un fragmento.
  • El [1.]reemplazo 1corresponde al cambio en lo que se rastrea.
  • Los {(\{)}%+}%incrementos adicionales de cada elemento en esa partición y los {1+}%agregados 1a la partición.
  • 0se convierte [0](en golf 1,) como parte del cambio en lo que se rastrea, pero debido a que necesita permanecer como una matriz cuando se antepone a otra, necesita el extra [ ].
  • La suma simple se {+}*convierte en una selección ponderada de las particiones, combinada con una suma de su recuento.
  • El (;`quita el recuento de la salida y hace que la partición en un formato agradable.

Marco de prueba

;7000,{;
  '5'

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

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

Ajuste el 7000 inicial si desea ejecutar un número diferente de pruebas. Tenga en cuenta que esto es demasiado lento para una demostración en línea.


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

Este es el mismo método que la respuesta de TheBestOne, pero utiliza una matriz simple en lugar de un mapa. Además, en lugar de devolver la partición aleatoria como a List, las imprime en la consola.

A continuación se muestra un programa de prueba que lo ejecuta 100000 veces. Por ejemplo n=5, todos los conjuntos estuvieron dentro del 0.64% de un 1/7 perfecto en mi última ejecución.

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
A pesar de haber jugado golf la Math.minllamada a (k<n?k:n), puede ir más allá, abandonando por completo y sólo hacer dos comprobaciones: b<k&b++<n. También puede deshacerse fácilmente de la n>0parte del bucle condicional (ya que se n>0&b<nreduce a b<ncuándo bse garantiza que no sea negativo).
Peter Taylor

@PeterTaylor Gracias. Echando otro vistazo, permítanme deshacerme de la declaración de devolución adicional y la intdeclaración por separado también.
Geobits

3

CJam, 64 56 bytes

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

Puedes probarlo con este script:

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

Explicación

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
Debería eliminar la parte incorrecta de su respuesta "no golf muy bien")
anatolyg

@anatolyg eliminado. Pero creo que todavía es posible eliminar algunos bytes. Soy demasiado vago para hacer eso.
jimmy23013

3

Pyth, 64 bytes

Utiliza /programming//a/2163753/4230423 excepto que a) No hay caché ya que Pyth se memoriza automáticamente, b) Imprime cada uno en lugar de agregarlo a la lista, yc) se traduce a Pyth.

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

Publicaré una explicación de esto cuando tenga tiempo, pero aquí está el código de Python correspondiente:

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

Editar: Finalmente llegué a hacer la explicació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

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

Sin golf:

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

Construya una matriz cuadrada donde cada celda (m, n) refleje el número de particiones mcuyo número más grande es n, según el extracto de Knuth @feersum tan amablemente citado. Por ejemplo, 5,2nos da 2 porque hay dos particiones válidas 2,2,1y 2,1,1,1. 6,3nos da 3 para 3,1,1,1, 3,2,1y 3,3.

Ahora podemos determinar determinísticamente la pth partición. Aquí, estamos generando pcomo un número aleatorio, pero puede modificar ligeramente el script, por lo que pes un 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

Ahora podemos demostrar de manera determinista que cada resultado depende únicamente 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

Por lo tanto, volviendo al original donde p se genera aleatoriamente, podemos estar seguros de que cada resultado es igualmente probable.


No estoy seguro acerca de su ejemplo 5,2. ¿No deberían ser las dos particiones (2,2,1)y(2,1,1,1,1) (ya que los dos que ha enumerado tienen números mayores que 2).
Martin Ender

Tienes razón, tengo las cosas retorcidas. Hay dos particiones con dos componentes, y dos particiones que comienzan con 2. Me refería a lo último.
dcsohl

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}

Sin golf:

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

Sigue la misma estructura que la gran solución de @ dcsohl en Octave y, por lo tanto, también se basa en el extracto de Knuth publicado por @feersum.

Lo editaré más adelante si puedo encontrar una solución más creativa en R. Mientras tanto, cualquier entrada es bienvenida.


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

Llamar con a(n) . Devuelve unList deInteger s

Sangrado:

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 y golfizado

Cómo funciona esto: podemos calcular cuántas particiones de un número entero n hay en el tiempo O ( n 2 ). Como efecto secundario, esto produce una tabla de tamaño O ( n 2 ) que luego podemos usar para generar la k partición de n , para cualquier entero k , en O ( n ).

Deje que total = el número de particiones. Escoja un número aleatorio k de 0 a total de - 1. Generar el k ésimo partición.

Como de costumbre , las sugerencias son bienvenidas :)


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]

Realiza recursivamente un diccionario d, con claves que krepresentan un par (n,m)mediante k=67*n+m(utilizando el garantizado n<=65). La entrada es la tupla del número de particiones nenm partes, y una partición aleatoria. Los recuentos se calculan mediante la fórmula recursiva (gracias a feersum por señalarlo)

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

y la partición aleatoria se actualiza seleccionando una de sus dos ramas con probabilidad proporcional a su recuento. La actualización se realiza agregando se realiza agregando un1 para la primera rama e incrementando cada elemento para la segunda.

Tuve muchos problemas para obtener valores fuera de límites my ndar recuentos de cero. Al principio, usé un diccionario que por defecto es un recuento de 0 y una lista vacía. Aquí, estoy usando una lista y rellenándola con esta entrada predeterminada en su lugar. Los índices negativos hacen que la lista se lea desde su final, lo que da una entrada predeterminada que no se acerca al final como se ha alcanzado nunca, y los parámetros envolventes solo tocan una región donde m>n.


1

80386 código máquina, 105 bytes

Hexdump del 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 una función C: void random_partition(int n, int result[]);. Devuelve el resultado como una lista de números en el búfer proporcionado; no marca el final de la lista de ninguna manera, pero el usuario puede descubrir el final acumulando los números; la lista termina cuando la suma es igual a n.

Cómo usar (en 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');
}

Ejemplo de salida (con n = 64):

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

Esto requiere muchas explicaciones ...

Por supuesto, utilicé el algoritmo que todos los demás también usaron; No había ninguna opción con el requisito sobre la complejidad. Entonces no tengo que explicar demasiado el algoritmo. De todas formas:

Denoto por f(n, m)el número de particiones de nelementos que usan partes no mayores que m. Los almaceno en una matriz 2-D (declarada en C como f[65][64]), donde está el primer índice ny el segundo m-1. Decidí que apoyarn=65 era demasiado problema, así que lo abandoné ...

Aquí hay un código C que calcula esta tabla:

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

Este código tiene un estilo ofuscado, por lo que se puede convertir a lenguaje ensamblador fácilmente. Calcula los elementos hasta f(n, n), que es el número de particiones de nelementos. Cuando finaliza este código, la variable temporal ccontiene el número necesario, que puede usarse para seleccionar una partición aleatoria:

int index = rand() % c;

Más tarde, esto indexse convierte al formato requerido (lista de números) usando la tabla generada.

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 también está optimizado para la conversión al lenguaje ensamblador. Hay un pequeño "error": si la partición no contiene ningún 1número al final, el último bucle se encuentra n = 0y genera un 1elemento innecesario . Sin embargo, no duele, porque el código de impresión rastrea la suma del número y no imprime este número extraño.

Cuando se convierte en ensamblaje en línea, este código se ve así:

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

Algunas cosas divertidas a tener en cuenta:

  • Generar un número aleatorio requiere solo 3 bytes de código de máquina ( rdrandinstrucción)
  • Por coincidencia, el tamaño de la tabla es 64, por lo que el tamaño de una fila es de 256 bytes. Utilizo esto para mantener índices de fila en registros de "byte alto" como ah, lo que me da una multiplicación automática por 256. Para aprovechar esto, sacrifiqué el soporte paran = 65 . Espero poder ser excusado por este pecado ...
  • La asignación de espacio en la pila se realiza restando 0x4100 del registro del puntero de la pila esp. ¡Esta es una instrucción de 6 bytes! Al volver a agregar este número, logré hacerlo en 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
    
  • ¡Al depurar esta función en MS Visual Studio, descubrí que se bloquea cuando escribe datos en el espacio que asignó en la pila! Después de investigar un poco, descubrí algún tipo de protección de desbordamiento de pila: el sistema operativo parece asignar solo un rango muy limitado de direcciones virtuales para la pila; Si una función accede a una dirección demasiado lejos, el SO asume que es una saturación y mata el programa. Sin embargo, si una función tiene muchas variables locales, el sistema operativo hace algo de "magia" adicional para que funcione. Entonces tengo que llamar a una función vacía que tiene una gran matriz asignada en la pila. Después de que esta función regrese, se asignan páginas VM de pila adicionales y se pueden usar.

        void make_stack()
        {
            volatile int temp[65 * 64];
            temp[0] = 999; // have to "use" the array to prevent optimizing it out
        }
    
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.