通过重复添加2个数字来生成任何数字


14

您将获得一台具有两个16位寄存器的计算机,xy。寄存器被初始化x=1y=0。机器唯一可以执行的操作是加法65536。即:

  • x+=y- x被替换(x + y) mod 65536; y不变
  • y+=x -类似地 y
  • x+=x- x被替换2x mod 65536; 仅在x偶数时合法
  • y+=y -类似地 y

目标是在其中一个寄存器(xy)中获得预定数量。

编写一个程序或子例程,该程序或子例程接收一个数字(在stdin,,argv函数参数,堆栈顶部或任何其他常规位置),并输出一个程序以获取该数字。输出应该转到任何其他常规输出设备stdout,或者(如果您的语言没有stdout)。

输出程序最多可以达到100%加上最佳值的2个步骤。也就是说,如果最短的获取目标编号的程序包含n步骤,则您的解决方案不能超过2n+2。此限制是为了避免“过于简单”的解决方案(例如,计算1、2、3,...),但不需要完全优化;我希望最短的程序最容易找到,但不能确定...

例如:输入=25。输出:

y+=x
x+=y
x+=y
x+=x
x+=x
x+=x
y+=x

另一个示例:对于任何斐波那契数,输出具有此交替模式。对于输入= 21,输出为

y+=x
x+=y
y+=x
x+=y
y+=x
x+=y
y+=x

最短的代码(以字节为单位)获胜。

(这个难题的灵感来自于我最近必须生成的16位处理器的一些代码)

PS:我想知道-最佳程序最长的数字是多少?


9
出于好奇,为什么x+=x只有x偶数才合法?同样对于最短的程序,我认为类似BFS的东西也可以工作。
Sp3000

到达目标后,您可能希望继续到达下一个目标号码;为了有可能到达任何目标,其中一个数字必须是奇数。我不想无休止地瞄准目标,以避免不必要的复杂性,但其精神是允许这样的目标……
anatolyg 2014年

我已更改了对步数的限制,因此对于目标数0或1,不需要将输出程序为空。
anatolyg 2014年

3
如果x+=x仅对偶数xs有效,那么输入25 double 3的示例又如何呢?
bcsb1001 2015年

Answers:


2

CJam,31岁

就像@Tobia的答案一样,我的算法也受到@CChak的答案的启发而被无耻地了。但是,利用CJam的黑魔法,我设法对算法进行了更小的实现。

在这里尝试。

打高尔夫球:

qi{_4%!:X)/X!-'xX+"
y+="@}h;]W%`

取消高尔夫:

qi          "Read input and convert to integer.";
{           "Do...";
  _4%!:X    "Assign (value mod 4 == 0) to X.";
  )/X!-     "If X, divide value by 2. If not X, decrement value.";
  'xX+      "If X, put 'y' on the stack. If not X, put 'x' on the stack.";
  "
y+="        "Put '\ny+=' on the stack.";
  @         "Rotate top 3 elements of stack left so the value is on top.";
}h          "... while value is not zero.";
;           "Discard zero value on stack.";
]W%         "Collect stack into array and reverse.";

如果我错了,请指正我,但我认为在使用类似算法的答案中使用65536模运算是不必要的。我对问题进行了解释,以便我们可以假定输入将是有效的无符号16位整数,并且该算法的任何中间值或结果也将是。


关于删除mod 65536的一个有趣的观点。我认为您有一个很好的理由,即“预定数量”必须在0-65535之间。
CChak

8

佩尔107 97

第一篇文章,就这样。

sub h{($i)=@_;return if(($i%=65536)==0);($i%4==0)?do{h($i/2);say"y+=y";}:do{h($i-1);say"y+=x";}}

这符合所有寄存器加法条件,但是我没有进行详尽的检查以查看我的答案是否始终在最佳步数的2n + 2之内。但是,它完全在每个斐波那契数的限制之内。

这是更详细的细分

sub h{                           # Declare the subroutine, it should be called referencing an integer value
   ($i)=@_;                      # Assign the i variable to the integer used in the call
   return if(($i%=65536)==0);    # Check for base condition of called by 0, and enforce modulo from the start.
   ($i%4==0) ?                   # If the value passed is even, and will result in an even number if we halve it
   do{h($i/2);say"y+=y";}        # Then do so and recurse 
   :do{h($i-1);say"y+=x";}       # Otherwise "subtract one" and recurse
}                                # Note that the print statements get called in reverse order as we exit.

正如我所提到的,这是我第一次打高尔夫球,因此我相信这可以得到改善。另外,我不确定是否必须在递归调用中计算初始子例程调用,否则可能会使我们增加一些字符。

有趣的是,通过放宽仅偶数可以“加倍”的要求,我们可以将代码减少11个字节*,并提高寄存器操作数的“效率”。我把它包括在这里很有趣:

sub h{($i)=@_;return if(($i%=65536)==0);($i%2==0)?do{h($i/2);say"y+=y";}:do{h($i-1);say"y+=x";}}

开始附录:

真的很喜欢这个问题,在过去的几周里,我一直在反复研究它。以为我会发表我的结果。

一些数字:

使用BFS算法找到最佳解决方案,在前2 ^ 16个数字中,只有18个数字需要23个步骤。它们是:58558、59894、60110、61182、61278、62295、62430、62910、63422、63462、63979、64230、64314、4486、64510、64698、64854、65295。

使用上述递归算法,在45次操作时,“最困难”的数字是65535。(65534取44,有14个数取43步)65535与最佳值的最大偏差为45 vs22。23步差为2n + 1。(只有三个数字命中2n:65534、32767、32751。)除了琐碎的(零步)情况外,在定义的范围内,递归方法的平均值约为最佳解的1.4倍。

底线:对于数字1-2 ^ 16,递归算法永远不会超过定义的2n + 2阈值,因此答案是有效的。我怀疑对于较大的寄存器/更多的位,它与最佳解决方案之间的距离会太远。

我用来创建BFS的代码草率,占用大量内存,没有注释,并且故意不包含在内。所以...您不必相信我的结果,但是我对他们很有信心。


非BFS解决方案,太好了!
anatolyg 2014年

我认为,即使是最病理的情况,您也不会超过4倍(因为我只知道最佳解决方案的下限)。还是不错的。
理性主义者

7

Python 3,202个字节

def S(n):
 q=[(1,0,"")];k=65536
 while q:
  x,y,z=q.pop(0)
  if n in{x,y}:print(z);return
  q+=[((x+y)%k,y,z+"x+=y\n"),(x,(x+y)%k,z+"y+=x\n")]+[(2*x%k,y,z+"x+=x\n")]*(~x&1)+[(x,2*y%k,z+"y+=y\n")]*(~y&1)

(感谢@rationalis几个字节)

这是一个非常基本的解决方案。我希望我能更好地打最后一线,但目前我没有主意。致电S(25)

该程序仅执行简单的BFS,不进行缓存,因此非常慢。这是S(97),用于一些示例输出:

y+=x
x+=y
x+=x
x+=x
x+=x
x+=x
y+=x
y+=x
x+=y

5

Dyalog APL,49个字符/字节*

{0=N←⍵|⍨2*16:⍬⋄0=4|N:⎕←'y+=y'⊣∇N÷2⋄⎕←'y+=x'⊣∇N-1}

算法无耻地受到@CChak的启发。

例:

    {0=N←⍵|⍨2*16:⍬⋄0=4|N:⎕←'y+=y'⊣∇N÷2⋄⎕←'y+=x'⊣∇N-1} 25
y+=x
y+=x
y+=y
y+=x
y+=x
y+=y
y+=y
y+=x

取消高尔夫:

{
    N←(2*16)|⍵                 # assign the argument modulo 65536 to N
    0=N: ⍬                     # if N = 0, return an empty value (that's a Zilde, not a 0)
    0=4|N: ⎕←'y+=y' ⊣ ∇N÷2     # if N mod 4 = 0, recurse with N÷2 and *then* print 'y+=y'
    ⎕←'y+=x' ⊣ ∇N-1            # otherwise, recurse with N-1 and *then* print 'y+=x'
}

* Dyalog APL支持传统字符集,该字符集具有映射到高128字节值的APL符号。因此,仅使用ASCII字符和APL符号的APL程序可以视为字节==字符。


3

Python,183

def S(n):
 b,c,e=16,'x+=x\n','x+=y\n';s=d='y+=x\n';a=i=0
 if n<2:return
 while~n&1:n>>=1;a+=1
 while n:n>>=1;s+=[e,c][i]+d*(n&1);i=1;b-=1
 while a:s+=[c,c*b+e*2][i];i=0;a-=1
 print(s)

我不能保证这是偶数最优程序的2倍之内,但是它是有效的。对于所有有效输入0 <= n < 65536,它实际上是瞬时的,并生成最多33条指令的程序。对于任意的寄存器大小n(固定该常数之后),O(n)最多使用2n+1指令会花费一些时间。

一些二进制逻辑

任何奇数n可以在31步达到:做y+=x,获得x,y = 1,1,然后不断倍增xx+=x(为第一加倍做x+=y,因为x是奇数有开始)。x将会以这种方式达到2的所有幂(只是左移),因此您可以y通过将2的相应幂加起来来将其任意一位设置为1。由于我们使用的是16位寄存器,除对于第一个,需要花一倍的力气才能完成y+=x,我们最多可以得到31个操作。

任何偶数n仅是2的幂,称其为a乘以奇数,即为m;即n = 2^a * m或等效地n = m << a。使用上面的过程来获取m,然后x通过左移使其重置为0,x+=y进行设置x = m。进行设置,然后继续加倍x,第一次使用x+=y,然后使用x+=x

不管a是什么,都需要16-a转移的x时间y=ma重置的时间x=0。之后将发生另一种a变化。因此,总共使用了班次。最多需要设置一些位,而每个位都需要一个。最后,当将其设置为m,时,我们需要执行一个额外的步骤。因此,最多需要33个步骤才能获得偶数。xx=m16+a16-amy+=xx=0x+=y

当然,您可以将其通用化为任何大小的寄存器,在这种情况下,它总是最多占用奇数位和偶数位整数的2n-12n+1ops n

最优性

该算法生成的程序对于奇数几乎是最佳的(即,2n+2如果n在最小步数之内)。对于给定的奇数n,如果第mk位为前导1,则任何程序都至少需要m走几步才能到达x=ny=n,因为最快增加寄存器值的操作是x+=xy+=y(即加倍),并且需要m加倍才能达到m从1开始的第th个比特。由于此算法最多执行2m步骤(每加倍最多执行两个步骤,因此移位最多执行一个步骤,而对则执行一次y+=x),因此任何奇数都将以接近最佳的方式表示。

偶数也不是那么好,因为它总是x在其他任何事情之前都要使用16个操作进行重置,例如,可以在5个步骤之内达到8。

有趣的是,上述算法从不使用y+=y,因为y它始终保持为奇数。在这种情况下,它实际上可能会找到仅3个操作的受限集的最短程序。

测试中

# Do an exhaustive breadth-first search to find the shortest program for
# each valid input
def bfs():
    d = {(0,1):0}
    k = 0xFFFF
    s = set(range(k+1))
    current = [(0,1)]
    nexts = []
    def add(pt, dist, n):
        if pt in d: return
        d[pt] = dist
        s.difference_update(pt)
        n.append(pt)
    i = 0
    while len(s) > 0:
        i += 1
        for p in current:
            x,y = p
            add((x,x+y&k), i, nexts)
            add((y,x+y&k), i, nexts)
            if y%2 == 0: add(tuple(sorted((x,y+y&k))), i, nexts)
            if x%2 == 0: add(tuple(sorted((x+x&k,y))), i, nexts)
        current = nexts
        nexts = []
        print(len(d),len(s))

# Mine (@rationalis)
def S(n):
    b,c,e=16,'x+=x\n','x+=y\n';s=d='y+=x\n';a=i=0
    if n<2:return ''
    while~n&1:n>>=1;a+=1
    while n:n>>=1;s+=[e,c][i]+d*(n&1);i=1;b-=1
    while a:s+=[c,c*b+e*2][i];i=0;a-=1
    return s

# @CChak's approach
def U(i):
    if i<1:return ''
    return U(i//2)+'y+=y\n' if i%4==0 else U(i-1)+'y+=x\n'

# Use mine on odd numbers and @CChak's on even numbers
def V(i):
    return S(i) if i % 2 == 1 else U(i)

# Simulate a program in the hypothetical machine language
def T(s):
    x,y = 1,0
    for l in s.split():
        if l == 'x+=x':
            if x % 2 == 1: return 1,0
            x += x
        elif l == 'y+=y':
            if y % 2 == 1: return 1,0
            y += y
        elif l == 'x+=y': x += y
        elif l == 'y+=x': y += x
        x %= 1<<16
        y %= 1<<16
    return x,y

# Test a solution on all values 0 to 65535 inclusive
# Max op limit only for my own solution
def test(f):
    max_ops = 33 if f==S else 1000
    for i in range(1<<16):
        s = f(i); t = T(s)
        if i not in t or len(s)//5 > max_ops:
            print(s,i,t)
            break

# Compare two solutions
def test2(f,g):
    lf = [len(f(i)) for i in range(2,1<<16)]
    lg = [len(g(i)) for i in range(2,1<<16)]
    l = [lf[i]/lg[i] for i in range(len(lf))]
    print(sum(l)/len(l))
    print(sum(lf)/sum(lg))

# Test by default if script is executed
def main():
    test()

if __name__ == '__main__':
    main()

我编写了一个简单的测试,以检查我的解决方案对于所有有效输入(0 <= n < 65536)的确产生正确的结果,并且从未超过33个步骤。

此外,我尝试进行实证分析,以将我的解决方案的输出与最佳输出进行比较-但是,事实证明,广度优先搜索效率低下,无法为每个有效输入获取最小输出长度n。例如,使用BFS查找输出n = 65535不会在合理的时间内终止。不过,我已经离开bfs()并愿意提出建议。

但是,我确实针对@CChak(在此处以Python实现U)测试了自己的解决方案。我预计我的情况会更糟,因为对于偶数较小的情况,它的效率极低,但是在整个范围内以两种方式进行平均,矿山的产出长度平均缩短了10.8%至12.3%。我以为这可能是由于我自己的奇数解决方案效率更高,所以V在奇数上使用mine,在偶数上使用@CChak,但是V介于两者之间(比约短10%,比约U长3%S)。


1
201字节中有很多逻辑!
anatolyg'1

@analtolyg我能说什么,我喜欢数学并且有点摆弄。我可能会研究其他方法,因为偶数解决方案还有改进的余地。
理性主义者

哇,x,y='xy'直到现在我都还没有意识到。不幸的是,我想不出一种c*b+e*2%格式简洁重写的方法。
理性

啊,我没意识到你在其他地方用过它。是我还是S(2)输出的真的很长?
Sp3000

不幸的是,使用我的解决方案,每个偶数至少需要执行19步(S(2)是19时最短的步)。我不跟踪xy明确的,所以即使x达到2第二步之后,仍然继续到重置x为0。我感觉好像必须有一个更好的解决方案,但由于还没有我想不出一。
理性主义者
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.