Отримання 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
Спробуйте в Інтернеті!