Получение 39 байтов
Это объяснение того, как я получил 39-байтовое решение, которое Деннис и Джонатан Фрех также нашли отдельно. Или, скорее, это объясняет, как можно прийти к ответу задним числом, намного лучше, чем мой настоящий путь к нему, который был полон грязных рассуждений и тупиков.
n=0
exec"print n;n=n+2^-(n+2^n)%3;"*400
Написание этого немного меньше в гольфе и с большим количеством паренов, это выглядит так:
n=0
for _ in range(400):
print n
n=(n+2)^(-((n+2)^n))%3
Битовые соотношения
Мы начнем с идеи из моего 47-байтового решения, чтобы вывести все числа в форме, в n=2*k+b
которой происходит k
обратный отсчет, 0,1,...,399
и b
это бит четности, который делает общее число равным 1.
Напишем par(x)
для бита четности из x
, то есть XOR ( ^
) все биты в x
. Это 0, если есть четное число 1-бит (число - зло), и 1, если есть нечетное число 1-бит. Ибо n=2*k+b
мы имеем par(n) = par(k)^b
, поэтому для достижения зла par(n)==0
нам нужно b=par(k)
, то есть последний бит n
должен быть четным битом предыдущих битов.
Мои первые попытки были играть в гольф на экспрессирующие par(k)
, сначала непосредственно с bin(k).count('1')%2
, а затем с битами .
Обновления четности
Тем не менее, казалось, не было короткого выражения. Вместо этого это помогло понять, что есть больше информации для работы. Вместо того, чтобы просто вычислять битовую четность текущего числа,
k ----> par(k)
мы можем обновить битовую четность при увеличении k
до k+1
.
k ----> par(k)
|
v
k+1 ----> par(k+1)
То есть, так как мы считаем k=0,1,2,...
, нам просто нужно поддерживать текущий битовый паритет, а не вычислять его с нуля каждый раз. Обновление четности битов par(k+1)^par(k)
- это четность количества бит, перевернутых при переходе от k
к k+1
, то есть par((k+1)^k)
.
par(k+1) ^ par(k) = par((k+1)^k)
par(k+1) = par(k) ^ par((k+1)^k)
Форма (k+1)^k
Теперь нам нужно вычислить par((k+1)^k)
. Может показаться, что мы ничего не получили, потому что вычисление четности битов - это именно та проблема, которую мы пытаемся решить. Но числа, выраженные как, (k+1)^k
имеют форму 1,3,7,15,..
, которая на единицу меньше степени 2, факт, часто используемый в битовых хакерских атаках . Давайте посмотрим, почему это так.
Когда мы увеличиваем k
, эффект двоичных переносов состоит в том, чтобы инвертировать последние 0
и все 1
справа, создавая новое ведение, 0
если их не было. Например, взятьk=43=0b101011
**
101011 (43)
+ 1
------
= 101100 (44)
101011 (43)
^101100 (44)
------
= 000111 (77)
Столбцы, вызывающие перенос, отмечены значком *
. Они 1
изменяют на a 0
и передают бит переноса 1
, который продолжает распространяться влево до тех пор, пока не попадет 0
в k
, который изменится на 1
. Любые биты дальше слева не затрагиваются. Таким образом, когда k^(k+1)
проверяет , какие позиции бит изменить , k
чтобы k+1
он находит позиции крайнего правые 0
и в 1
«с до его права. То есть измененные биты образуют суффикс, поэтому результатом являются 0, за которыми следует одна или несколько единиц. Без ведущих нулей существуют двоичные числа 1, 11, 111, 1111, ...
, которые на единицу меньше степени 2.
вычисления par((k+1)^k)
Теперь, когда мы понимаем, что (k+1)^k
это ограничено 1,3,7,15,...
, давайте найдем способ вычислить битовую четность таких чисел. Здесь, полезный факт, что 1,2,4,8,16,...
чередуются по модулю 3
между 1
и 2
, так как 2==-1 mod 3
. Итак, взятие 1,3,7,15,31,63...
по модулю 3
дает 1,0,1,0,1,0...
, что в точности соответствует их битовым соотношениям. Отлично!
Итак, мы можем сделать обновление par(k+1) = par(k) ^ par((k+1)^k)
как
par(k+1) = par(k) ^ ((k+1)^k)%3
Используя b
в качестве переменной, в которой мы сохраняем четность, это выглядит так
b^=((k+1)^k)%3
Написание кода
Собирая это вместе в коде, мы начинаем k
и бит четности b
как в 0
, затем многократно печатаем n=2*k+b
и обновляем b=b^((k+1)^k)%3
и k=k+1
.
46 байт
k=b=0
exec"print 2*k+b;b^=(k+1^k)%3;k+=1;"*400
Попробуйте онлайн!
Мы убрали скобки вокруг k+1
в ((k+1)^k)%3
потому , что Python старшинство делает добавление первой в любом случае, странно , как это выглядит.
Улучшения кода
Хотя мы можем добиться большего, работая напрямую с одной переменной n=2*k+b
и выполняя обновления непосредственно над ней. Делать k+=1
соответствует n+=2
. И, обновление b^=(k+1^k)%3
соответствует n^=(k+1^k)%3
. Здесь, k=n/2
перед обновлением n
.
44 байта
n=0
exec"print n;n^=(n/2+1^n/2)%3;n+=2;"*400
Попробуйте онлайн!
Мы можем сократить n/2+1^n/2
(помните, что это (n/2+1)^n/2
), переписав
n/2+1 ^ n/2
(n+2)/2 ^ n/2
(n+2 ^ n)/2
Так как /2
удаляет последний бит, не имеет значения, делаем ли мы это до или после копирования. Итак, у нас есть n^=(n+2^n)/2%3
. Мы можем сохранить еще один байт, отметив , что по модулю 3
, /2
эквивалентно *2
эквивалентно -
, отметив , что n+2^n
даже поэтому разделение актуально сращивание без напольного покрытия. Это даетn^=-(n+2^n)%3
41 байт
n=0
exec"print n;n^=-(n+2^n)%3;n+=2;"*400
Попробуйте онлайн!
Наконец, мы можем объединить операции n^=c;n+=2
в n=(n+2)^c
, где c
немного. Это работает, потому что ^c
действует только на последний бит и +2
не заботится о последнем бите, поэтому операции коммутируют. Опять же, приоритет позволяет нам опускать парены и писать n=n+2^c
.
39 байт
n=0
exec"print n;n=n+2^-(n+2^n)%3;"*400
Попробуйте онлайн!