Як на землі llhuii вивів Злі числа в 42 байтах Python?


71

Це питання поради щодо гри в гольф в Python, що стосується питання Evil Numbers про Anarchy Golf .

Число є злом, якщо його двійкове розширення має парне число 1. Завдання полягає в тому, щоб надрукувати перші 400 злих чисел 0,3,5,...,795,797,798, по одному на рядок.

Подання Python 2 керує llhuii з 42-байтовим рішенням. Наступний кращий - 46 байт мітчем, за ним - п’ять 47-байтних подань. Здається, llhuii знайшов щось по-справжньому магічне, що ухилялося від багатьох сильних гольфістів Python протягом 2 років. Економлять 4 або 5 байт - величезний для такого короткого гольфу.

Таблиця балів Python 2

Я все ще на 47 байт. Я сподіваюся, що ми зможемо розбити цю загадку як спільнота. Якщо ми отримаємо відповідь спільно, я б подав її під іменами всіх, хто зробив свій внесок. Відповідь на це запитання може бути фрагментом коду чи новою ідеєю або фрагментом аналізу. Якщо ви llhuii, будь ласка, поки не псуйте це нам.

Хоча подання не розкрито, оскільки ця проблема нескінченна, нам надаються деякі результати. Перемога на виграш зайняла 0,1699 секунди, набагато довше, ніж будь-яка інша, що передбачає неефективний метод. Зі статистики байтів, з 42 символів 23 - буквено-цифрові [0-9A-Za-z]та 19 - символи ASCII. Це означає, що в розчині llhuii немає пробілу.

Ви можете перевірити свій код на проблемній сторінці , вибравши Python зі спадного меню та завантаживши .pyфайл. Зауважте, що:

  • Використовується Python 2.7
  • Ваш код повинен бути повноцінною програмою, яка друкує
  • Немає жодних у цю проблему, як от
  • Вашій програмі потрібно просто надрукувати 400 заданих значень, навіть якщо це буде розбито на великі значення
  • Програми мають 2 секунди для запуску
  • Програми можуть закінчуватися з помилкою
  • Ви можете використовувати exec; "exec відмовлено" відноситься до оболонки exec

2
Також може бути достойним зазначити, що ця послідовність - це "Показники нулів у послідовності Тю-Морза A010060". (джерело: oeis )
Conor O'Brien

Відповіді:


51

Це не те саме рішення, що і llhuii, але це також 42 байти.

n=0;exec'print n;n^=(n^n+2)%3/2;n+=2;'*400

Спробуйте в Інтернеті!

Завдяки @JonathanFrech зараз ми на 40 байтах.

n=0;exec'print n;n=n+2^(n^n+2)/2%3;'*400

Спробуйте в Інтернеті!

Є ще один байт, який потрібно зберегти, загалом 39.

n=0;exec'print n;n=n+2^-(n^n+2)%3;'*400

Спробуйте в Інтернеті!


1
З цікавості, як ви знаєте, що 42-байтна версія не є такою ж, як llhuii? (Я ніколи не брав участі в Anarchy Golf)
Луїс Мендо

6
@LuisMendo Вкладка Статистика містить 23 алфавітно-цифрові байти та 19 символів ASCII, тому пробілу немає. Якщо не llhuii писав print+n, їх рішення повинно відрізнятися від мого.
Денніс

Так, ви можете отримати деяку інформацію, навіть якщо ви не знаєте коду. Це мило. Дякую!
Луїс Мендо

Як ви вважаєте, чи є шанс для 38? Теоретично існує певна ступінь свободи, щоб потенційно усунути -знак, переміщуючи print~nабо print-nвикористовуючи, &або ~, хоча я нічого не маю на роботі. Крім того, n=0;exec"print n;d=n^n+2;n^=d^-d%3;"*400досить 40 байт.
xnor

print-nздається малоймовірним, оскільки між набором бітів nі і не існує легкої взаємозв'язку -n. print~nтеоретично звучить більш перспективно, але я не можу одержати нижче 40 байт при такому підході.
Денніс

28

Отримання 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

Спробуйте в Інтернеті!


13

Це дає моє 47-байтове рішення (xnor's) та мислення, яке привело мене до цього. Не читайте цього, якщо ви хочете зрозуміти це самостійно.

Природна перша ідея - це повторити цифри від 0 до 799, друкуючи лише ті, які мають парне число 1 у двійкових.

52 байти

for n in range(800):
 if~bin(n).count('1')%2:print n

Спробуйте в Інтернеті!

Тут, ~біт приймає доповнення так, щоб перемикатись even<->oddу підрахунку та надавати тритилеве значення лише на парних числах.

Ми можемо покращити цей метод, генеруючи всі значення замість фільтрування. Зауважте, що вихідні значення - це числа від 0 до 399, кожне з яких додається трохи, щоб зробити число 1 біт парним.

0 = 2*0 + 0
3 = 2*1 + 1
5 = 2*2 + 1
6 = 2*3 + 0
...

Отже, nчисло число або 2*n+bз b=0або b=1. Біт bможна знайти, порахувавши 1's у бітах nта взявши модуль count 2.

49 байт

for n in range(400):print 2*n+bin(n).count('1')%2

Спробуйте в Інтернеті!

Ми можемо скоротити два байти за 2*допомогою повторення 0,2,4,..., що не дає шансу підрахунку 1s. Ми можемо це зробити, використовуючи execцикл, який працює в 400 разів і збільшуючи nпо 2 у кожній петлі.

47 байт

n=0;exec"print n+bin(n).count('1')%2;n+=2;"*400

Спробуйте в Інтернеті!

І це моє 47-байтне рішення. Я підозрюю більшість, якщо не всі інші 47-байтні рішення однакові.


1
Чи execдозволено ваш 47 байт ?
Джонатан Фрех

1
@JonathanFrech Так, коли на сторінці написано "exec відмовлено", це стосується не Python, execа командного рядка exec.
xnor

9

Подання Python 3 на llhuii

Ось подання Python 3 для Evil Numbers під час написання:

введіть тут опис зображення

llhuii, ймовірно, перенесли свій трюк на Python 3 і придумали таке рішення

  • На 3 байти довше, ніж їх рішення Python 2, і
  • має 45 - (25 + 18) = 2 байти пробілу.

Переносячи xnor 47B на Python 3 буквально, ми отримуємо це 50B:

n=0;exec("print(n+bin(n).count('1')%2);n+=2;"*400)

Я подав його як ppcg(xnor). (Він додає круглі дужки до execта print, які тепер є функціями.) Він має різні статистичні дані коду від інших відповідей Python 3, у яких всі мають деяку кількість пробілів. Цікаво!

Існує коротший спосіб переписати його ( execяк правило, втрачає свою конкурентну перевагу в Python 3):

n=0
while n<800:print(n+bin(n).count('1')%2);n+=2

Це 49 байт. Я подав його як ppcg(xnor,alternative). Це два байти пробілу, як і відповідь llhui! Це призводить мене до думки, що відповідь Python 3 llhuii виглядає таким чином (новий рядок, потім whileцикл). Так що llhuii, ймовірно, використовуються execв Python 2 і whileв Python 3, як і ми; цим пояснюється розбіжність пробілів.


Наш 47B став 49B на Python 3. Що цікаво, зараз, що 42B llhuii не став 44B, він став 45B! Щось про рішення llhuii має ще один байт у Python 3. Це може означати різноманітні речі.

  • Перше, що спадає на думку, - це поділ : можливо, llhuii використовує /в Python 2, який став //у Python 3. (Якщо вони рахують двох, як ми, то, n/2можливо, вони будуть використані для переміщення nназад вправо одним бітом?)

  • Інша річ, яка спадає на думку - це одинакові оператори після друку . Наш print blahстав print(blah)(1 байт додатково), але якби llhuii написав щось на зразок print~-blahу Python 2, він став би print(~-blah)в Python 3.

  • Можливо, є й інші ідеї. Будь ласка, дай мені знати.

Статистика коду для всіх рішень Py3, включаючи моє зараз:

введіть тут опис зображення


1
Цікавим є те, що рішення Python 3 значно швидше, ніж рішення Python 2. Або вони використовують якусь функцію Python, яка стала більш ефективною в Python 3, або це не простий порт, зрештою (можливо, вони знайшли рішення Python 3, яке коротше, ніж прямий порт).
Джонатан Фрех

2
Виконання на анаголі має величезну дисперсію, я прокоментував ОП, що час виконання llhuii тут змушує мене думати, що їхній час виконання Py2 - це просто червона оселедець / шум
Lynn

Крім того , я вважаю , XNOR знайшов дуже схожий трюк і поліпшення на ньому (не може бути , що багато способів надрукувати погані чисел, правильно?!) І їх рішення досить швидко!
Лінн

7

Інші підходи

1) Використання формули для A001969

Замість того, щоб перетворити на бінарне, можливо скористатися такою формулою (від OEIS ):

a(1) = 0
for n > 1: a(n) = 3*n-3-a(n/2) if n is even
           a(n) = a((n+1)/2)+n-1 if n is odd

Мені дуже погано в гольфі в Python, тому я навіть не збираюся намагатися. Але ось швидка спроба в JS.

NB: Я не думаю, що це було б дійсним поданням JS, оскільки це просто заповнення масиву, не відображаючи його. І навіть так, це на 5 байт довше, ніж поточне найкраще рішення JS (це 45 байт). Але це все одно не в цьому.

for(a=[n=0,3];n<199;)a.push(2*++n+a[n],6*n+3-a[n])

Сподіваємось, це може дати деяке натхнення.

Використання масиву, мабуть, не є хорошою ідеєю, оскільки його потрібно ініціалізувати та оновити. Можливо, більш ефективно (розмір коду) використовувати замість цього рекурсивну функцію , яка б пояснила, чому виграшне рішення займає більше часу, ніж інші.

2) Побудова послідовності Thue-Morse із замінами

Теоретично цей код повинен працювати:

n=0;a="1";b="0";exec"t=a;a+=b;b+=t;print(int(b[n]))+n;n+=2;"*400

Спробуйте в Інтернеті! (версія для запуску обмежена 20 умовами)

Він обчислює послідовність Thue-Morse з послідовними підмінами і шукає позицію 1-х (Злі номери) в одному циклі.

Але:

  • У його теперішній формі він занадто довгий
  • Це швидко призводить до переповнення пам'яті

3) Побудова послідовності Thue-Morse за допомогою бітових операцій

Починаючи з прямого визначення Вікіпедії послідовності Чт-Морса , я прийшов до цього алгоритму (перехід на JS ... вибачте):

for(e=n=0;n<799;)(e^=!(((x=n++^n)^x/2)&170))||console.log(n)

де ми відстежуємо поточну злість послідовності в e і використовуємо 170 як бітмаску непарних біт у байті.


Мені подобається ідея рекурсивної функції, але Python дуже поганий в плані котла: f=lambda n:_ for n in range(400):print f(n)вже займає 43 байти. Можливо, є спосіб моделювати рекурсію, будуючи масив, на який посилається, або масив, який додає майбутні елементи до кінця.
xnor

2
Крім того , рішення llhuii не має ні одного місця в ньому, так що він не використав def, for, while, lambda(з параметром , по крайней мере), і т.д.
Стівен

@Stephen Щось подібне while~0:print~1не потребує пробілів.
Джонатан Фрех

У способі №3, ((x=n++^n)^x/2)здається, дещо багатослівний, щоб знайти найменший встановлений біт. Весь цей безлад можна замінити на ++n&-n. Спробуйте в Інтернеті!
прим

@primo Я не знаю, що я тут думав і як я прийшов до цієї громіздкої формули. ¯ \ _ (ツ) _ / ¯
Арнольд

5

Підхід вкладених лічильників

У мене є ідея щодо іншого підходу, але я недостатньо досвідчений в гольф-пітонах, тому я залишу це для вас, хлопці, щоб розглянути як інший можливий вихідний пункт для гольфу.

Ідея, що не має волі:

n=0
i=1
for _ in"01":
 i^=1
 for _ in"01":
  i^=1
  for _ in"01":
   i^=1
   for _ in"01":
    i^=1
    for _ in"01":
     i^=1
     for _ in"01":
      i^=1
      for _ in"01":
       i^=1
       for _ in"01":
        i^=1
        for _ in"01":
          i^=1
          if n<800:print i+n
          n+=2

Спробуйте в Інтернеті!

Дев'ять рівнів вкладеної глибини, усі петлі однакові, тому, на мій погляд, їх слід будувати exec"something"*9+"deepest stuff". На практиці я не знаю, чи можна зробити щось подібне за допомогою циклу.

Що варто врахувати для гольфу:

  • можливо, є якась інша можливість циклу двічі, окрім циклу for (я намагався підходити до квінету, щоб рядок, який потрібно виконати, двічі перейшов до себе як аргумент форматування, але моя голова вибухнула).

  • також може бути краща альтернатива if n<800:, яка тут потрібна, оскільки в іншому випадку ми б продовжували друкувати злі цифри до 2 ^ 10



Можливо, спробуйте зрозуміти вкладені списки замість вкладених циклів?
Спарр

@Sparr Проблема тоді полягає в тому, щоб насправді надрукувати цифри. У Python 2 print- це твердження, а не функція, і тому воно не може з'явитися всередині розуміння.
Джонатан Фрех

можливоprint '\n'.join([[[[[[[[[foo]foo]foo]foo]foo]foo]foo]foo]foo])
Sparr

@Sparr Тоді проблема полягає у вирівнюванні списку; str.joinпрацює лише у списках, що містять рядки, а символи додаткового списку не повинні друкуватися. Само форматування займе значну кількість байтів.
Джонатан Фрех

5

Ідея: Більш короткий паритет

Потрібно багато символів, bin(n).count('1')%2щоб обчислити паритет бітового рахунку. Може бути, арифметичний спосіб коротший, особливо зважаючи на обмежену довжину бітів.

Милий спосіб однакової довжини - int(bin(n)[2:],3)%2інтерпретація двійкового значення як базової 3(або будь-якої непарної бази). На жаль, 4 байти витрачають на видалення 0bпрефікса. Це також працює int(bin(n)[2:])%9%2.

Інша ідея походить від комбінування бітів за допомогою xor. Якщо nмає двійкове представлення abcdefghi, то

n/16 = abcde
n%16 =  fghi

r = n/16 ^ n%16 has binary representation (a)(b^f)(c^g)(d^h)(e^i)

Отже, r=n/16^n%16є зло, якщо і тільки якщо воно nє злом. Потім можна повторити , що , як s=r/4^r%4, значення sв 0,1,2,3, з яких 1і 2не є злом, перемикає з 0<s<3.

52 байти

n=0;exec"r=n/16^n%16;print(0<r/4^r%4<3)+n;n+=2;"*400

Спробуйте в Інтернеті!

Це вийшло набагато довше. Хоча багато поворотів, хоча розбити число, як перевірити остаточне число (можливо, таблиця пошуку на основі бітів). Я підозрюю, що вони можуть зайти лише далеко.


чи була б можливість використовувати to_bytesфункцію цілих чисел? Сумніваюсь, але що варто врахувати :)
HyperNeutrino

@HyperNeutrino Я думаю, що це лише Python 3?
xnor

yup my bad: / rip
HyperNeutrino

9
Просто використовуйте 0b: int(bin(n),13)%2! : D
Noodle9

3
Прогрес! Трюк Noodle9 пропонує 44-байтове рішення:n=0;exec"print~int(bin(n),13)%2+n;n+=2;"*400
Лінн

4

За конструкцією n+n^nце завжди зло, але мої бідні навички Python могли придумати лише 61-байтове рішення:

for n in sorted(map(lambda n:n+n^n,range(512)))[:400]:print n

Дякуємо @Peilonrayz за збереження 5 байт та @ Mr.Xcoder за збереження 1 байта:

for n in sorted(n^n*2for n in range(512))[:400]:print n

55 байт : for n in sorted(n^n*2for n in range(512))[:400]:print n. n+n^nте саме, щоn^n*2
містер Xcoder

3

Ідея: A006068 ("a (n) є сірим кодом у n")

Ідея Ніла сортувати все 2n XOR nмене заінтригувало, тому я спробував знайти індекси, що стоять за цим сортом. Я написав цей код , і він виявляє, що ми можемо написати щось подібне:

for n in range(400):x=a(n);print 2*x^x

Де a(n)A006068 (n). Спробуйте в Інтернеті!

Однак це передбачає, що у нас є деякий короткий шлях до обчислення A006068. Це вже 38 байт, якщо припустити, що ми можемо обчислити його в 4 байти ( a(n)частина). Реальна реалізація (у заголовку TIO) набагато довша за це. Думаю, не надто сподіваюсь на це.


3

Ідея: зменшити на XOR

Якщо ви XOR всі шматочки nразом, це буде 0для зла і 1для не-зла. Це можна зробити за допомогою рекурсивної функції (яка, таким чином, може зайняти більше часу?), Наприклад:

f=lambda n:f(n/2^n&1)if n>1else-~-n

Це повертає 1 за зло.

Це 35 байт і перевіряє, чи є число чи не злим. На жаль, filterце вже 6 байт, тому це не було оптимальним рішенням дослівно, але ця ідея, ймовірно, може бути гольф.


Я думаю, ви можете зробити f=lambda n:n>1and f(n/2^n&1)or-~-nдля -1 байт.
Ерік Аутгольфер

@EriktheOutgolfer Я намагався, але це викликає помилки при f(n/2^n&1)поверненні 0 ...
HyperNeutrino

2

Спосіб заміни: {1 -> {1, -1}, -1 -> {-1, 1}}

Ви також можете зробити цю заміну 10 разів {1 -> {1, -1}, -1 -> {-1, 1}}, а потім вирівняти і перевірити позиції 1

ось код математики

(F = Flatten)@
Position[F@Nest[#/.{1->{1,-1},-1->{-1,1}}&,1,10],1][[;; 400]] - 1

Як би ти це зробив у python?
Aneesh Durg

2
@AneeshDurg Ви знайдете щось цікаве в цьому рішенні? подумайте поза коробкою, і ви можете знайти свій шлях до сенсу життя AKA 42
J42161217
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.