Obteniendo 39 bytes
Esta es una explicación de cómo obtuve una solución de 39 bytes, que Dennis y JonathanFrech también encontraron por separado. O, más bien, explica cómo se puede llegar a la respuesta en retrospectiva, de una manera que es mucho mejor que mi camino real, que estaba lleno de razonamientos fangosos y callejones sin salida.
n=0
exec"print n;n=n+2^-(n+2^n)%3;"*400
Escribiendo esto un poco menos golfizado y con más padres, esto se ve así:
n=0
for _ in range(400):
print n
n=(n+2)^(-((n+2)^n))%3
Paridades de bits
Comenzamos con una idea de mi solución de 47 bytes para generar todos los números del formulario n=2*k+b
donde k
cuenta 0,1,...,399
y b
es un bit de paridad que iguala el número total de 1.
Escribamos par(x)
por la paridad de bits de x
, que es el xor ( ^
) en el que están todos los bits x
. Esto es 0 si hay un número par de 1 bits (el número es malo) y 1 si hay un número impar de 1 bits. Para n=2*k+b
, tenemos par(n) = par(k)^b
, así que para lograr el mal par(n)==0
necesitamos b=par(k)
, es decir, el último bit de n
ser la paridad de bits de los bits anteriores.
Mis primeros esfuerzos en el golf fueron expresar par(k)
, al principio directamente con bin(k).count('1')%2
, y luego con la manipulación de bits .
Actualizaciones de paridad
Aún así, no parecía haber una expresión corta. En cambio, ayudó a darse cuenta de que hay más información para trabajar. En lugar de simplemente calcular la paridad de bits del número actual,
k ----> par(k)
podemos actualizar el bit de paridad como incrementamos k
a k+1
.
k ----> par(k)
|
v
k+1 ----> par(k+1)
Es decir, dado que estamos contando k=0,1,2,...
, simplemente necesitamos mantener la paridad de bits actual en lugar de calcularla desde cero cada vez. La actualización del bit de paridad par(k+1)^par(k)
es la paridad del número de bits volteado al ir de k
a k+1
, es decir par((k+1)^k)
.
par(k+1) ^ par(k) = par((k+1)^k)
par(k+1) = par(k) ^ par((k+1)^k)
Forma de (k+1)^k
Ahora necesitamos calcular par((k+1)^k)
. Puede parecer que no hemos llegado a ninguna parte porque calcular la paridad de bits es exactamente el problema que estamos tratando de resolver. Pero, los números expresados como (k+1)^k
tienen la forma 1,3,7,15,..
, es decir, uno por debajo de una potencia de 2, un hecho que a menudo se usa en hacks de bits . Veamos por qué es eso.
Cuando incrementamos k
, el efecto de los acarreos binarios es invertir el último 0
y todo 1
a su derecha, creando un nuevo líder 0
si no hubiera ninguno. Por ejemplo, tomek=43=0b101011
**
101011 (43)
+ 1
------
= 101100 (44)
101011 (43)
^101100 (44)
------
= 000111 (77)
Las columnas que causan un acarreo están marcadas con *
. Estos tienen un 1
cambio en ay 0
pasan un bit de acarreo de 1
, que se sigue propagando a la izquierda hasta que llega a un 0
in k
, que cambia a 1
. Cualquier parte más a la izquierda no se ve afectada. Por lo tanto, cuando se k^(k+1)
comprueba qué bit posiciones cambian k
a k+1
, encuentra las posiciones de la derecha 0
y de la 1
's de su derecha. Es decir, los bits modificados forman un sufijo, por lo que el resultado son 0 seguidos de uno o más 1. Sin los ceros a la izquierda, hay números binarios 1, 11, 111, 1111, ...
que están uno debajo de una potencia de 2.
Informática par((k+1)^k)
Ahora que entendemos que (k+1)^k
se limita a 1,3,7,15,...
, busquemos una manera de calcular la paridad de bits de dichos números. Aquí, un hecho útil es ese 1,2,4,8,16,...
módulo alternativo 3
entre 1
y 2
, desde 2==-1 mod 3
. Entonces, tomar 1,3,7,15,31,63...
módulos 3
da 1,0,1,0,1,0...
, que son exactamente sus paridades de bits. ¡Perfecto!
Entonces, podemos hacer la actualización par(k+1) = par(k) ^ par((k+1)^k)
como
par(k+1) = par(k) ^ ((k+1)^k)%3
Usando b
como la variable en la que estamos almacenando la paridad, esto parece
b^=((k+1)^k)%3
Escribiendo el código
Poniendo esto junto en el código, comenzamos k
y el bit de paridad b
en ambos 0
, luego imprimimos n=2*k+b
y actualizamos repetidamente b=b^((k+1)^k)%3
y k=k+1
.
46 bytes
k=b=0
exec"print 2*k+b;b^=(k+1^k)%3;k+=1;"*400
Pruébalo en línea!
Hemos eliminado parens alrededor k+1
de ((k+1)^k)%3
porque Python precedencia hace la adición primero de todos modos, raro como parece.
Mejoras de codigo
Sin embargo, podemos hacerlo mejor si trabajamos directamente con una sola variable n=2*k+b
y realizamos las actualizaciones directamente en ella. Hacer k+=1
corresponde a n+=2
. Y, la actualización b^=(k+1^k)%3
corresponde a n^=(k+1^k)%3
. Aquí, k=n/2
antes de actualizar n
.
44 bytes
n=0
exec"print n;n^=(n/2+1^n/2)%3;n+=2;"*400
Pruébalo en línea!
Podemos acortar n/2+1^n/2
(recuerde que esto es (n/2+1)^n/2
) reescribiendo
n/2+1 ^ n/2
(n+2)/2 ^ n/2
(n+2 ^ n)/2
Como /2
elimina el último bit, no importa si lo hacemos antes o después de xor-ing. Entonces, tenemos n^=(n+2^n)/2%3
. Podemos ahorrar otro byte por señalar que en módulo 3
, /2
es equivalente a *2
es equivalente a -
, señalando que n+2^n
es aun así la división es reducir a la mitad real y sin suelo. Esto dan^=-(n+2^n)%3
41 bytes
n=0
exec"print n;n^=-(n+2^n)%3;n+=2;"*400
Pruébalo en línea!
Finalmente, podemos combinar las operaciones n^=c;n+=2
en n=(n+2)^c
, donde c
es un poco. Esto funciona porque ^c
actúa solo en el último bit y +2
no le importa el último bit, por lo que las operaciones conmutan. Nuevamente, la precedencia nos permite omitir parens y escribir n=n+2^c
.
39 bytes
n=0
exec"print n;n=n+2^-(n+2^n)%3;"*400
Pruébalo en línea!