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)
Não posso garantir que isso fique dentro do dobro do programa ideal para números pares, mas é eficiente. Para todas as entradas válidas 0 <= n < 65536
, é essencialmente instantânea e gera um programa com no máximo 33 instruções. Para um tamanho de registro arbitrário n
(depois de fixar essa constante), levaria O(n)
tempo com, no máximo, 2n+1
instruções.
Alguma Lógica Binária
Qualquer número ímpar n
pode ser alcançado dentro de 31 etapas: do y+=x
, obtendo x,y = 1,1
e, em seguida, continuando dobrando x
com x+=x
(para o primeiro duplicado x+=y
, pois x
é estranho, para começar). x
atingirá todas as potências de 2 dessa maneira (é apenas um deslocamento para a esquerda) e, portanto, você poderá definir y
1 como uma adicionando a potência correspondente a 2. Como estamos usando registradores de 16 bits, e cada bit, exceto para a primeira leva uma duplicação para alcançar e outra y+=x
para definir, temos no máximo 31 ops.
Qualquer número par n
é apenas uma potência de 2, ligue a
, vezes um número ímpar, ligue m
; ou seja n = 2^a * m
, ou equivalente n = m << a
. Use o processo acima para obter e m
, em seguida, redefina x
movendo-o para a esquerda até chegar a 0. Faça a x+=y
para definir x = m
e continue dobrando x
, na primeira vez, usando x+=y
e subseqüentemente x+=x
.
Seja o que a
for, são necessárias 16-a
mudanças x
para obter y=m
e outras a
alterações para redefinir x=0
. Outras a
mudanças x
ocorrerão depois x=m
. Portanto, um total de 16+a
turnos é usado. Existem até 16-a
bits que precisam ser configurados para obter m
, e cada um deles precisará de um y+=x
. Finalmente, precisamos de uma etapa extra x=0
para configurá-la como m x+=y
,. Portanto, são necessários no máximo 33 etapas para obter qualquer número par.
Você pode, é claro, generalizar isso para qualquer registro de tamanho, caso em que sempre leva no máximo 2n-1
e 2n+1
ops para números n
inteiros ímpares e pares , respectivamente.
Optimalidade
Esse algoritmo produz um programa que é quase ideal (ou seja, 2n+2
se n
é o número mínimo de etapas) para números ímpares. Para um determinado número ímpar n
, se o m
th bit for o primeiro 1, qualquer programa executa pelo menos m
etapas para chegar a x=n
ou y=n
, uma vez que a operação que aumenta os valores dos registros mais rapidamente é x+=x
ou y+=y
( ou seja, duplicação) e é necessária m
duplicação para obter o m
th bit de 1. Como esse algoritmo executa no máximo algumas 2m
etapas (no máximo duas por duplicação, uma para o turno e outra y+=x
), qualquer número ímpar é representado quase idealmente.
Números pares não são tão bons, pois sempre usam 16 ops para redefinir x
antes de qualquer outra coisa, e 8, por exemplo, pode ser alcançado em 5 etapas.
Curiosamente, o algoritmo acima nunca usa y+=y
, pois y
é sempre mantido ímpar. Nesse caso, ele pode realmente encontrar o programa mais curto para o conjunto restrito de apenas 3 operações.
Teste
# 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()
Eu escrevi um teste simples para verificar se minha solução realmente produz resultados corretos, e nunca ultrapassa 33 etapas, para todas as entradas válidas ( 0 <= n < 65536
).
Além disso, tentei fazer uma análise empírica para comparar a saída da minha solução com a saída ideal - no entanto, verifica-se que a pesquisa pela primeira vez é muito ineficiente para obter o comprimento mínimo de saída para cada entrada válida n
. Por exemplo, o uso do BFS para encontrar a saída para n = 65535
não termina em um período de tempo razoável. No entanto, eu participei bfs()
e estou aberto a sugestões.
No entanto, testei minha própria solução no @ CChak's (implementado em Python aqui como U
). Eu esperava que o meu fizesse pior, já que é drasticamente ineficiente para números pares menores, mas, em toda a faixa de duas maneiras, a mina produziu uma produção de comprimento em média 10,8% a 12,3% menor. Eu pensei que talvez isso fosse devido à melhor eficiência da minha própria solução em números ímpares, então V
usa o meu em números ímpares e @ CChak em números pares, mas V
está no meio (cerca de 10% menor que U
, 3% maior que S
).
x+=x
legal apenas sex
é par? Também para o programa mais curto, acho que algo como o BFS poderia funcionar.