Enumerar programas válidos de Brainf ** k

41

Golunar / Unario es una forma de codificar todos válidos Brainfuck programas, pero no es una enumeración, ya que la mayoría de los números naturales no corresponden a un programa válido.

Para el propósito de este desafío, asuma una cinta doblemente infinita y sin comentarios, es decir, un programa Brainfuck es válido si y solo si consiste únicamente en los caracteres <>+-.,[]y todos los corchetes izquierdo y derecho coinciden.

Por ejemplo, el programa vacía, ,[+][-]., [>+<[--].]y +[+[+][+[+]+]+]+.son programas Brainfuck válida, mientras que ][, y a[]no lo son.

Tarea

Escriba un programa o función que acepte un programa Brainfuck válido como entrada y devuelva un número natural ( 1 , 2 , 3 , ...), con las siguientes restricciones:

  • La salida generada debe ser diferente para todos los programas válidos de Brainfuck.

  • Para cada número natural n , debe haber un programa Brainfuck válido que, cuando se proporciona como entrada, genera la salida n .

Reglas adicionales

  • Dado un programa Brainfuck de 100 o menos bytes, su programa o función debe finalizar en un minuto.

    Esto significa que no puede iterar sobre todos los programas válidos de Brainfuck hasta que coincida con la entrada.

  • Aplican reglas estándar de .

Dennis
fuente
3
Estaba pensando en codificarlo como octal, pero los corchetes coincidentes hacen que esto sea complicado.
DankMemes
¿Es el programa vacío un programa válido de Brainfuck? ¿Debe ser mapeado a un entero natural también?
orlp
99
¿Por qué la votación cerrada? Esta es una pregunta fascinante y el problema tiene el tamaño y la variedad para un gran golf.
trichoplax
1
@orlp Sí, el programa vacío satisface la definición anterior
Dennis
3
Todavía estoy esperando ver una respuesta escrita en Brainfuck ...
Michael Hampton

Respuestas:

16

Python 3, 443 158 155 154 134 131 128 124 117 116 115 bytes

c=d=C=D=0
for e in input():v='[<>,.-+]'.find(e);d=d*8+v;c+=c<0<6<v;c-=d>1>v;C,D=(c,C+1,d,D)[v>6::2]
print(-~D*8**C)

Varios bytes gracias a Sp3000 y Mitch Schwartz: D

Cómo funciona esto:

Esto asigna todos los programas BF válidos a todos los programas BF posibles, válidos o no válidos, que no comienzan con una [relación uno a uno. Después de eso, el nuevo programa simplemente se convierte en octal.

Aquí está la fórmula de mapeo:

  1. Separe un programa BF en 3 partes. La primera parte es el prefijo más grande que consta de solo [caracteres. La tercera parte es el postfix más grande que consta de solo ]caracteres. La segunda parte es el medio.
  2. Desechar la primera parte. Estos pueden ser recalculados más tarde.
  3. Retire todos los ]corchetes en la tercera parte que coincidan con los [corchetes en la segunda parte. Estos también pueden ser recalculados más tarde.
  4. Concatenar la segunda y tercera parte juntas.

Si no comprende esta explicación, puede encontrar una explicación extendida en el chat a partir de aquí .

Como referencia, aquí están los primeros 20 programas:

1 : 
2 : <
3 : >
4 : ,
5 : .
6 : -
7 : +
8 : []
9 : <[]
10 : <<
11 : <>
12 : <,
13 : <.
14 : <-
15 : <+
16 : [<]
17 : >[]
18 : ><
19 : >>
20 : >,

Aquí están los primeros 1000 programas: http://pastebin.com/qykBWhmD
Aquí está el programa que usé para generarlos: http://ideone.com/e8oTVl

Aqui esta Hello, World!:

>>> ++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++.
457711481836430915510337664562435564418569135809989841510260388418118348571803953323858180392373
El numero uno
fuente
Pequeñas objeciones: no puede asignar un programa a 0 .
Dennis
@ Dennis ¿Cuenta un programa vacío como un programa?
Decaimiento Beta
@BetaDecay Sí: codegolf.stackexchange.com/questions/55363/…
TheNumberOne
@Dennis Lo arreglaré cuando juegue al golf.
TheNumberOne
3
Esto es ingenioso
orgulloso Haskeller
13

Python 2, 157 bytes

def f(s,o=0,d=0,D={}):T=s,o,d;x=D[T]=D[T]if T in D else~o and 0**o+sum(f(s[1:],cmp(c,"[")%-3-~o,d or cmp(c,s[0]))for c in"+,-.<>[]")if s else~d<0==o;return+x

Todavía se ve bastante golfable, pero estoy publicando esto por ahora. Utiliza la recursividad con un poco de almacenamiento en caché. Molesto, D.getno hace un corto circuito para el almacenamiento en caché, por lo que no puedo guardar 9 bytes de esa manera ...

El mapeo prioriza primero la longitud, luego el orden lexicográfico sobre el orden "][><.-,+"(ver ejemplos de salida a continuación). La idea principal es comparar prefijos.

La variable orealiza un seguimiento del número de [paréntesis aún abiertos para el prefijo actual, mientras que la variable dtoma uno de los tres valores que indican:

  • d = 1: El prefijo actual es lexicográficamente anterior a s. Agregue todos los programas con este prefijo y longitud <= s,
  • d = -1: El prefijo actual es lexicográficamente posterior a s. Agregue todos los programas con este prefijo y longitud < s.
  • d = 0: El prefijo actual es un prefijo de s, por lo que podríamos cambiar da 1 o -1 más adelante.

Por ejemplo, si tenemos s = "[-]"y nuestro prefijo actual es p = "+", ya que pes posterior a la slexicografía, solo sabemos agregar los programas que comienzan con los pcuales son estrictamente más cortos que s.

Para dar un ejemplo más detallado, supongamos que tenemos un programa de entrada s = "-[]". La primera expansión recursiva hace esto:

  (o == 0)               # Adds a program shorter than s if it's valid
                         # For the first expansion, this is 1 for the empty program
+ f(s[1:], o=-1, d=1)    # ']', o goes down by one due to closing bracket
+ f(s[1:], o=1, d=1)     # '[', o goes up by one due to opening bracket
+ f(s[1:], o=0, d=1)     # '>'
+ f(s[1:], o=0, d=1)     # '<'
+ f(s[1:], o=0, d=1)     # '.', d is set to 1 for this and the previous branches
                         # since they are lexicographically earlier than s's first char
+ f(s[1:], o=0, d=0)     # '-', d is still 0 since this is equal to s's first char
+ f(s[1:], o=0, d=-1)    # ',', d is set to -1 for this and the later branches
                         # since they are lexicographically later than s's first char
+ f(s[1:], o=0, d=-1)    # '+'

Nota cómo no utilizan los prefijos en la recursividad - todos nos preocupamos por ellos es capturado a través de las variables d, oy el programa de entrada de la contracción s. Notarás muchas repeticiones arriba: aquí es donde entra el almacenamiento en caché, lo que nos permite procesar programas de 100 caracteres dentro del límite de tiempo.

Cuando sestá vacío, miramos (d>=0 and o==0), que decide si devolver 1 (cuente este programa porque es lexicográficamente temprano / igual y el programa es válido), o 0 (no cuente este programa).

Cualquier situación con o < 0retornos inmediatos 0, ya que cualquier programa con este prefijo tiene más de ]s [, y por lo tanto no es válido.


Las primeras 20 salidas son:

 1
> 2
< 3
. 4
- 5
, 6
+ 7
[] 8
>> 9
>< 10
>. 11
>- 12
>, 13
>+ 14
<> 15
<< 16
<. 17
<- 18
<, 19
<+ 20

Usando el mismo ejemplo de Hello World que la respuesta de @ TheNumberOne:

>>> f("++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++.")
3465145076881283052460228065290888888678172704871007535700516169748342312215139431629577335423L
Sp3000
fuente
4

Python 2, 505 (sin golf)

Disfruté desarrollando este enfoque, pero es posible que no me moleste en jugarlo porque no es competitivo en comparación con otros enfoques. Lo estoy publicando por el bien de la diversidad y el posible interés estético. Implica recursividad y un poco de matemática.

F={0:1}

def f(n):
    if n not in F:
        F[n]=6*f(n-1) + sum(f(i)*f(n-2-i) for i in range(n-1))

    return F[n]

def h(x):
    if x=='': return 0

    if len(x)==1: return '+-<>,.'.find(x)

    if x[0]!='[':
        return h(x[0]) * f(len(x)-1) + h(x[1:])

    d=i=1
    while d:
        if x[i]==']': d-=1
        elif x[i]=='[': d+=1
        i+=1

    a=i-2
    b=len(x)-i

    return 6*f(a+b+1) + sum(f(i)*f(a+b-i) for i in range(a)) + h(x[1:i-1]) * f(b) + h(x[i:])

def g(x):
    return sum(f(i) for i in range(len(x))) + h(x) + 1

print g(raw_input())

La función f(n)cuenta el número de programas válidos de duración de brainfuck n. h(x)asigna programas de longitud na [0..f(n)-1], y g(x)es la función de clasificación biyectiva en cuestión.

La idea principal es que un programa no vacío puede comenzar con [o con uno de los 6 []caracteres no . En el primer caso, podemos iterar sobre las posibles ubicaciones de la coincidencia ]y la recurrencia en la parte cerrada y en la cola (donde cola significa la subcadena que sigue a la ]). En el último caso, podemos recurrir en la cola (donde cola significa soltar el primer carácter). Este razonamiento se puede usar tanto para contar como para calcular el rango.

Los programas más cortos siempre tendrán un rango inferior que los programas más largos, y el patrón de paréntesis es un factor determinante secundario. Los no []caracteres se ordenan según "+ - <>,". (que es arbitrario)

Por ejemplo con n=4tenemos estos casos:

zxxx
[]xx
[x]x
[xx]

donde zsignifica no []carácter y xrepresenta cualquier carácter, bajo la restricción de que ]tiene que coincidir con la inicial [. Los programas se clasifican según ese orden, y recursivamente en las xsubsecciones, con la sección izquierda priorizada sobre la sección derecha en los últimos casos. El cálculo del rango es similar a los sistemas de numeración de radix mixtos y fes importante para calcular la "radix" actual.

Mitch Schwartz
fuente
4

Esta respuesta es una prueba formal de la respuesta de TheNumberOne , Enumerar programas Brainf ** k válidos , donde puede ser un poco difícil entender los puntos finos por los que la enumeración es correcta. No es trivial entender por qué no hay algún programa no válido que se asigne a un número no cubierto por un programa válido.

A lo largo de esta respuesta, las mayúsculas se usan para denotar programas, y las variables en minúsculas se usan para funciones y enteros. ~ es el operador de concatenación.

Proposición 1:

Deje que la función f sea el programa descrito en esa respuesta. Entonces, para cada programa U existe un programa válido V tal que f (U) = f (V)

Definición 1:

Supongamos que g (X) sea el número [que aparece en el programa X y que h (X) sea el número ]que aparece.

Definición 2:

Defina P (x) para que sea esta función:

P(x) = "" (the empty program) when x <= 0
P(x) = "]" when x = 1
P(x) = "]]" when x = 2
etcetera

Definición 3:

Dado un programa X, denote que X1 es su prefijo de [caracteres más grande , X2 su centro y X3 su sufijo de ]caracteres más grande .

Prueba de proposición 1:

Si g (U) = h (U), entonces U es un programa válido, y podemos tomar V = U. (caso trivial).

Si g (U) <h (U), entonces podemos crear V al anteponer [símbolos n = h (U) - g (U) . Obviamente f (V) = f (U) ya que todos los [símbolos en el prefijo se eliminan.

Ahora considere g (U)> h (U). Definir T = U2 ~ U3. si g (T) <= h (T), entonces podemos construir V eliminando los [símbolos n = g (U) - h (U) .

Entonces podemos suponer que h (T) <g (T). Construir V = T ~ P (g (T) - h (T)).

Necesitamos tres pequeños hechos para proceder:

Reclamación 1: g (U2) = g (T)

U3 no contiene ningún [símbolo por su definición. Como T = U2 ~ U3, sus [símbolos están todos en la primera parte.

Reclamación 2: h (U3) <g (T)

Esto se deduce de señalar que h (T) <g (T) y h (U3) <h (U3 ~ U2) = h (T).

Reclamación 3: h (V3) = g (U2) - h (U2)

h(V3) = h(U3) + g(T) - h(T)                           using the construction of V
h(V3) = h(U3) + g(U2) + g(U3) - h(U2) - h(U3)         apply the definition of T
h(V3) = g(U2) - h(U2) *one term cancels, g(U3)        is always zero, as U3 contains only `]` symbols*

Ahora mostramos que f (V) = f (U).

f(U) = U2 ~ P(h(U3) - g(U2)) = U2                     claim 2, definition of P

f(V) = U2 ~ P(h(V3) - g(V2))
     = U2 ~ P(h(V3) - g(U2))
     = U2 ~ P(g(U2) - h(U2) - g(U2))                  claim 3
     = U2 ~ P(-h(U2))
     = U2                                             definition P

Esto completa la prueba. QED

Hagamos también la unicidad.

Proposición 2:

Sea U, V dos programas diferentes y válidos. Entonces f (U)! = F (V)

Esto es bastante sencillo en comparación con la propuesta anterior.

Supongamos que U2 = V2. Pero entonces la única forma de U y V pueden ser diffrent es mediante la adición o la eliminación de n [y ]símbolos para U1 y U3, respectivamente. Sin embargo, esto cambia la salida de f, ya que f contará el número de ]símbolos no coincidentes en el sufijo.

Por lo tanto, U2! = V2.

Obviamente, esto lleva a una contradicción. Como U2 y V2 están literalmente contenidos en la salida de f (U) yf (V) respectivamente, no pueden diferir, excepto en el 'borde', el lugar donde U2 está concatenado con U3. Pero el primer y último símbolo de U2 y V2 no pueden ser [o ]por definición, mientras que esos son los únicos símbolos permitidos en U1, U3, V1, V3, respectivamente, y de nuevo respectivamente. Así obtenemos U2 = V2. QED

áfido
fuente