Отримання 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
, який продовжує поширюватися ліворуч, поки він не потрапить на a, 0
в k
який змінюється 1
. Будь-які шматки ліворуч не впливають. Таким чином, коли k^(k+1)
перевіряє , які позиції біт змінити , k
щоб k+1
він знаходить позиції крайнього праві 0
і в 1
«с до його права. Тобто, змінені біти утворюють суфікс, тому результат 0 дорівнює наступний один чи більше 1-х. Без провідних нулів існують двійкові числа 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
видаляється останній біт, не має значення, чи будемо ми це робити до або після xor-ing. Отже, маємо 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
Спробуйте в Інтернеті!