枚举有效的Brainf ** k程序


41

Golunar / 一元是编码的所有有效的方式Brainfuck程序,但它不是一个枚举,因为最自然的号码不对应于一个有效的程序。

出于此挑战的目的,假设使用了无限的双倍磁带且没有任何注释,即Brainfuck程序在且仅当其仅由字符组成<>+-.,[]并且所有左括号和右括号都匹配时才有效。

例如,空的程序,,[+][-].[>+<[--].]并且+[+[+][+[+]+]+]+.是有效的Brainfuck方案,同时][,并a[]没有。

任务

写接受的有效Brainfuck程序作为输入并返回的自然数(程序或函数123,...),有以下限制:

  • 对于所有有效的Brainfuck程序,生成的输出必须不同。

  • 对于每个自然数n,必须有一个有效的Brainfuck程序,当该程序作为输入提供时,会生成输出n

附加规则

  • 对于100个字节或更少字节的Brainfuck程序,您的程序或函数必须在一分钟内完成。

    这意味着在匹配输入之前,您不能迭代所有有效的Brainfuck程序。

  • 适用标准规则。


3
我本来只是想将其编码为八进制,但匹配的括号使这个问题变得棘手。
DankMemes

空程序是否是有效的Brainfuck程序?还必须将其映射为自然整数吗?
orlp

9
为什么要近距离投票?这是一个令人着迷的问题而问题恰恰是大型高尔夫球的大小和多样性。
trichoplax

1
@orlp是的,该空程序满足上述定义
Dennis

3
仍在等待中,看到用Brainfuck写的答案...
Michael Hampton

Answers:


16

Python 3中,443 158 155 154 134 131个 128 124 117 116 115字节

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)

多亏了Sp3000和Mitch Schwartz:D的几个字节

工作原理:

这会将所有有效的BF程序[以一对一的比例映射到所有不以a开头的可能,有效或无效的BF程序。之后,新程序将简单地转换为八进制。

这是映射公式:

  1. 将BF程序分为3部分。第一部分是仅由[字符组成的最大前缀。第三部分是仅由]字符组成的最大后缀。第二部分是中间部分。
  2. 处理第一部分。这些可以稍后重新计算。
  3. 删除]第三部分中与[第二部分中的支架匹配的所有支架。这些也可以稍后重新计算。
  4. 将第二部分和第三部分连接在一起。

如果您不理解该说明,则可以从此处开始的聊天中找到扩展说明。

作为参考,以下是前20个程序:

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

这是前1000个程序:http : //pastebin.com/qykBWhmD
这是我用来生成它们的程序:http : //ideone.com/e8oTVl

这里是Hello, World!

>>> ++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++.
457711481836430915510337664562435564418569135809989841510260388418118348571803953323858180392373

次要问题:您无法将程序映射到0
丹尼斯

@Dennis空程序是否算作程序?
Beta Decay 2015年


@丹尼斯,我会在打高尔夫球时解决这个问题。
TheNumberOne

3
这很巧妙
骄傲的haskeller 2015年

13

Python 2,157字节

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

看起来仍然很适合打高尔夫球,但我现在将其发布。它使用递归和一些缓存。烦人的是,D.get缓存不会短路,所以我不能那样保存9个字节...

映射优先顺序是长度,然后是字典顺序"][><.-,+"(顺序如下)(请参见下面的输出示例)。主要思想是比较前缀。

该变量o跟踪[针对当前前缀仍处于打开状态的括号的数量,而该变量d采用表示以下内容的三个值之一:

  • d = 1:当前的前缀在字典上早于s。添加所有具有此前缀和长度的程序<= s
  • d = -1:当前前缀在字典上晚于s。添加所有具有此前缀和长度的程序< s
  • d = 0:当前前缀是的前缀s,因此以后我们可能会更改d为1或-1。

例如,如果我们有s = "[-]"并且我们的当前前缀是p = "+",因为p它比s字典上晚,我们只知道添加p严格比开头的程序s

为了给出更详细的示例,假设我们有一个输入程序s = "-[]"。第一个递归扩展执行此操作:

  (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)    # '+'

注意我们如何不实际使用前缀的递归-我们关心他们是通过变量捕获do而且收缩输入程序s。您会在上面注意到很多重复-这是缓存的来源,这使我们能够在规定的时间内很好地处理100个字符的程序。

s为空时,我们查看(d>=0 and o==0),它决定是返回1(对程序进行计数,因为它在字典上早/等于,并且程序有效),还是返回0(不对程序进行计数)。

与任何situtation o < 0立即返回0,因为以这个前缀任何程序有更多的]除S [,并因此无效。


前20个输出是:

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

使用与@TheNumberOne的答案相同的Hello World示例:

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

4

Python 2,505(不打高尔夫球)

我喜欢开发这种方法,但是我可能不会打高尔夫球,因为与其他方法相比,它没有竞争力。我出于多样性和可能的​​审美趣味发布它。它涉及递归和一些数学运算。

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

该函数f(n)计算有效的length的fuckfuck程序的数量nh(x)将长度n为的程序映射到[0..f(n)-1],这g(x)是所讨论的双射排名函数。

主要思想是,非空程序可以以[6个非[]字符或其中之一开始。在前一种情况下,我们可以遍历匹配的可能位置,]然后在封闭部分和尾部递归(其中tail表示后面的子字符串])。在后一种情况下,我们可以在尾巴上递归(尾巴意味着删除第一个字符)。此推理可用于计数和计算排名。

较短的程序总是比较长的程序具有较低的排名,并且括号模式是次要的决定因素。非[]字符根据“ +-<>,”排序。(这是任意的)。

例如与n=4我们有这些情况:

zxxx
[]xx
[x]x
[xx]

其中z代表非[]字符,x代表任何字符,但前提是字符]必须与initial匹配[。程序按照该顺序排序,并在x小节中递归排列,在后一种情况下,左节优先于右节。等级计算类似于混合基数系统,并且f对于计算当前“基数”很重要。


4

此答案是TheNumberOne的答案的正式证明,列举有效的Brainf ** k程序,在其中可能很难理解为什么枚举正确的要点。理解为什么没有无效程序映射到有效程序未涵盖的数字是很重要的。

在整个答案中,大写字母表示程序,小写变量表示函数和整数。〜是串联运算符。

主张1:

令函数f为该答案中描述的程序。然后对于每个程序U,都有一个有效的程序V,使得f(U)= f(V)

定义1:

令g(X)为[出现在程序X中的数目,令h(X)为出现在程序X中的数目]

定义2:

将P(x)定义为此函数:

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

定义3:

给定程序X,则将X1表示为最大[字符前缀,将X2表示为中心,而X3表示其最大后缀]

命题证明1:

如果g(U)= h(U),则U是有效程序,我们可以采用V = U。(平凡的情况)。

如果g(U)<h(U),则可以通过在n = h(U)-g(U)[符号前添加V来创建。显然f(V)= f(U),因为[删除了前缀中的所有符号。

现在考虑g(U)> h(U)。定义T = U2〜U3。如果g(T)<= h(T),则可以通过去除n = g(U)-h(U)[符号来构造V。

因此我们可以假设h(T)<g(T)。构造V = T〜P(g(T)-h(T))。

我们需要三个小事实来进行:

要求1: g(U2)= g(T)

U3 [的定义中不包含任何符号。由于T = U2〜U3,其[符号全部在第一部分。

要求2: h(U3)<g(T)

这是因为注意到h(T)<g(T)且h(U3)<h(U3〜U2)= h(T)。

要求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*

现在我们证明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

这样就完成了证明。优质教育

让我们也做唯一性。

主张2:

令U,V为两个不同的有效程序。然后f(U)!= f(V)

与先前的主张相比,这相当简单。

假设U2 = V2。但是,唯一可以区分U和V的方法是分别向U1和U3 添加或删除n []符号。但这会改变f的输出,因为f将计算]后缀中不匹配符号的数量。

因此,U2 = V2。

显然,这导致了矛盾。由于U2和V2实际上分别包含在f(U)和f(V)的输出中,因此它们不能不同,除了在“边沿”处(U2与U3串联在一起的位置)。但是U2和V2的第一个和最后一个符号不能是[]根据定义,而分别是U1,U3,V1,V3或分别允许的唯一符号。因此,我们得到U2 = V2。优质教育

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.