Enumerar programas Brainf ** k válidos

41

Golunar / Unary é uma maneira de codificar todos os programas válidos do Brainfuck , mas não é uma enumeração, pois a maioria dos números naturais não corresponde a um programa válido.

Para o propósito deste desafio, assuma uma fita duplamente infinita e nenhum comentário, ou seja, um programa Brainfuck é válido se e somente se consistir apenas nos caracteres <>+-.,[]e todos os colchetes esquerdo e direito coincidirem.

Por exemplo, o programa vazio, ,[+][-]., [>+<[--].]e +[+[+][+[+]+]+]+.são programas Brainfuck válidos, enquanto que ][, e a[]não são.

Tarefa

Escreva um programa ou função que aceite um programa Brainfuck válido como entrada e retorne um número natural ( 1 , 2 , 3 ,…), com as seguintes restrições:

  • A saída gerada deve ser diferente para todos os programas válidos do Brainfuck.

  • Para todo número natural n , deve haver um programa Brainfuck válido que, quando fornecido como entrada, gere a saída n .

Regras adicionais

  • Dado um programa Brainfuck de 100 ou menos bytes, seu programa ou função deve terminar em um minuto.

    Isso significa que você não pode repetir todos os programas válidos do Brainfuck até corresponder à entrada.

  • Aplicam-se as regras padrão de .

Dennis
fonte
3
Eu estava pensando em codificá-lo como octal, mas os colchetes correspondentes estão tornando isso complicado.
precisa saber é o seguinte
O programa vazio é um programa Brainfuck válido? Também deve ser mapeado para um número inteiro natural?
orlp 27/08/2015
9
Por que a votação apertada? Essa é uma pergunta fascinante e o problema tem o tamanho e a variedade de um ótimo golfe.
precisa saber é o seguinte
11
@orlp Sim, os satisfaz programa vazias a definição acima
Dennis
3
Ainda à espera de ver uma resposta escrita na Brainfuck ...
Michael Hampton

Respostas:

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)

Vários bytes graças ao Sp3000 e Mitch Schwartz: D

Como isso funciona:

Isso mapeia todos os programas válidos de BF em todos os programas possíveis, válidos ou inválidos de BF, que não começam com um [, na proporção de um para um. Depois disso, o novo programa é simplesmente convertido em octal.

Aqui está a fórmula de mapeamento:

  1. Separe um programa BF em 3 partes. A primeira parte é o maior prefixo que consiste apenas em [caracteres. A terceira parte é o maior postfix composto apenas por ]caracteres. A segunda parte é o meio.
  2. Descarte a primeira parte. Estes podem ser recalculados mais tarde.
  3. Remova todos os ]colchetes na terceira parte que correspondem aos [colchetes na segunda parte. Estes também podem ser recalculados mais tarde.
  4. Concatene a segunda e a terceira partes juntas.

Se você não entender esta explicação, poderá encontrar uma explicação detalhada no chat, começando aqui .

Para referência, aqui estão os 20 primeiros programas:

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

Aqui estão os primeiros 1000 programas: http://pastebin.com/qykBWhmD
Aqui está o programa que eu usei para gerá-los: http://ideone.com/e8oTVl

Aqui está Hello, World!:

>>> ++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++.
457711481836430915510337664562435564418569135809989841510260388418118348571803953323858180392373
O número um
fonte
Qualificação menor: você não pode mapear um programa para 0 .
Dennis
@Dennis Um programa vazio conta como um programa?
Decay Beta
@BetaDecay Sim: codegolf.stackexchange.com/questions/55363/...
TheNumberOne
@ Dennis Eu vou consertar isso quando eu jogar golfe.
TheNumberOne
3
Isso é engenhoso
proud 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

Ainda parece bastante jogável, mas estou postando isso por enquanto. Ele usa recursão com um pouco de cache. Irritantemente, D.getnão provoca um curto-circuito no cache, então não posso salvar 9 bytes dessa maneira ...

O mapeamento prioriza primeiro o comprimento, depois a ordem lexicográfica sobre a ordem "][><.-,+"(veja exemplos de saída abaixo). A idéia principal é comparar prefixos.

A variável ocontrola o número de [colchetes ainda abertos para o prefixo atual, enquanto a variável dutiliza um dos três valores que indicam:

  • d = 1: O prefixo atual é lexicograficamente anterior a s. Adicione todos os programas com esse prefixo e comprimento <= s,
  • d = -1: O prefixo atual é lexicograficamente posterior a s. Adicione todos os programas com esse prefixo e comprimento < s.
  • d = 0: O prefixo atual é um prefixo de s, portanto, podemos mudar dpara 1 ou -1 mais tarde.

Por exemplo, se tivermos s = "[-]"e nosso prefixo atual for p = "+", uma vez que pé posterior ao slexicograficamente, sabemos apenas adicionar os programas iniciados com os pquais são estritamente menores que s.

Para dar um exemplo mais detalhado, suponha que tenhamos um programa de entrada s = "-[]". A primeira expansão recursiva faz isso:

  (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 como nós realmente não usar os prefixos na recursão - todos nós se preocupa com eles é capturada através das variáveis d, oe o programa de entrada encolhendo s. Você notará muitas repetições acima - é aqui que entra o cache, permitindo que processemos programas de 100 caracteres bem dentro do prazo.

Quando sestá vazio, examinamos (d>=0 and o==0), que decide se deve retornar 1 (conte este programa porque é lexicograficamente precoce / igual e o programa é válido) ou 0 (não conte este programa).

Qualquer situação com o < 0retorno imediato 0, pois qualquer programa com esse prefixo tem mais ]s que [e, portanto, é inválido.


As primeiras 20 saídas são:

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

Usando o mesmo exemplo do Hello World que a resposta do @ TheNumberOne:

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

Python 2, 505 (não jogado)

Eu gostei de desenvolver essa abordagem, mas talvez não me incomode em jogar golfe porque não é competitiva em comparação com outras abordagens. Estou publicando por causa da diversidade e possível interesse estético. Envolve recursão e um pouco 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())

A função f(n)conta o número de programas válidos de foda cerebral n. h(x)mapeia programas de comprimento npara [0..f(n)-1]e g(x)é a função de classificação bijetiva em questão.

A idéia principal é que um programa não vazio possa começar com [ou com um dos 6 []caracteres não . No primeiro caso, podemos iterar sobre os possíveis locais da correspondência ]e recuar na parte fechada e na cauda (onde cauda significa a substring após a ]). No último caso, podemos recuar na cauda (onde cauda significa largar o primeiro caractere). Esse raciocínio pode ser usado tanto na contagem quanto na classificação da computação.

Programas mais curtos sempre terão classificação mais baixa do que programas mais longos, e o padrão de colchetes é um fator determinante secundário. Os não []caracteres são classificados de acordo com "+ - <>,". (que é arbitrário).

Por exemplo, com n=4estes casos:

zxxx
[]xx
[x]x
[xx]

onde zsignifica não []caractere e xrepresenta qualquer caractere, sob a restrição de que ele ]precisa corresponder à inicial [. Os programas são classificados de acordo com essa ordem e recursivamente nas xsubseções, com a seção esquerda priorizada sobre a seção direita nos últimos casos. O cálculo da classificação é semelhante aos sistemas de números de mistura mista e fé importante para calcular a "base" atual.

Mitch Schwartz
fonte
4

Esta resposta é uma prova formal da resposta de TheNumberOne , Enumerate Brainf ** k programas válidos , onde pode ser um pouco difícil entender os pontos positivos da razão pela qual a enumeração está correta. Não é trivial entender por que não há algum programa inválido que mapeie para um número não coberto por um programa válido.

Em toda essa resposta, maiúsculas são usadas para denotar programas e variáveis ​​em minúsculas são usadas para funções e números inteiros. ~ é o operador de concatenação.

Proposição 1:

Seja a função f o programa descrito nessa resposta. Então, para todo programa U existe um programa válido V tal que f (U) = f (V)

Definição 1:

Seja g (X) o número [que aparece no programa X e h (X) seja o número ]que aparece.

Definição 2:

Defina P (x) para esta função:

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

Definição 3:

Dado um programa X, denote X1 como seu maior prefixo de [caracteres, X2 seu centro e X3 seu maior sufixo de ]caracteres.

Prova da proposição 1:

Se g (U) = h (U), então U é um programa válido, e podemos usar V = U. (caso trivial).

Se g (U) <h (U), então podemos criar V acrescentando n = h (U) - g (U) [símbolos. Obviamente f (V) = f (U), pois todos os [símbolos no prefixo são removidos.

Agora considere g (U)> h (U). Defina T = U2 ~ U3. se g (T) <= h (T), então podemos construir V removendo n = g (U) - h (U) [símbolos.

Portanto, podemos assumir que h (T) <g (T). Construa V = T ~ P (g (T) - h (T)).

Precisamos de três pequenos fatos para prosseguir:

Reivindicação 1: g (U2) = g (T)

U3 não contém nenhum [símbolo por sua definição. Como T = U2 ~ U3, seus [símbolos estão todos na primeira parte.

Reivindicação 2: h (U3) <g (T)

Isso resulta da observação de que h (T) <g (T) eh (U3) <h (U3 ~ U2) = h (T).

Reivindicação 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*

Agora 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

Isso completa a prova. QED

Vamos fazer exclusividade também.

Proposição 2:

Seja U, V dois programas válidos diferentes. Então f (U)! = F (V)

Isso é bastante simples em comparação com a proposição anterior.

Vamos supor que U2 = V2. Mas a única maneira de U e V serem diferentes é adicionando ou removendo n [e ]símbolos a U1 e U3, respectivamente. No entanto, isso altera a saída de f, pois f contará o número de ]símbolos não correspondentes no sufixo.

Assim U2! = V2.

Obviamente, isso leva a uma contradição. Como U2 e V2 estão literalmente contidos na saída de f (U) ef (V), respectivamente, eles não podem diferir, exceto na 'borda', o local em que U2 é concatenado com U3. Mas o primeiro e o último símbolo de U2 e V2 não podem ser [ou ]por definição, enquanto esses são os únicos símbolos permitidos em U1, U3, V1, V3, respectivamente e respectivamente novamente. Assim, temos U2 = V2. QED

pulgão
fonte