Это вопрос подсказки для игры в гольф в Python относительно вопроса о Evil Numbers на Anarchy Golf .
Число является злом, если его двоичное расширение имеет четное число единиц. Задача состоит в том, чтобы напечатать первые 400 злых чисел 0,3,5,...,795,797,798
, по одному в строке.
Представления Python 2 возглавляются llhuii с 42-байтовым решением. Следующий лучший результат - 46 байтов за mitchs, сопровождаемые пятью 47-байтовыми представлениями. Кажется, что llhuii нашел нечто поистине волшебное, что ускользнуло от многих сильных игроков в Python более 2 лет. Экономия 4 или 5 байтов огромна для такого короткого гольфа.
Я все еще на 47 байтов. Я надеюсь, что мы сможем раскрыть эту загадку как сообщество. Если мы получим ответ совместно, я отправлю его под именами всех, кто внес свой вклад. Ответом на этот вопрос может быть фрагмент кода, новая идея или фрагмент анализа. Если вы llhuii, пожалуйста, не портите нам это.
Хотя представления не раскрываются, потому что эта проблема бесконечна, нам дают некоторые выводы. Победное представление заняло 0,1699 секунд, что намного дольше, чем любое другое, что указывает на неэффективный метод. Из статистики байтов, из 42 символов 23 являются буквенно-цифровыми [0-9A-Za-z]
и 19 являются символами ASCII. Это означает, что в решении llhuii нет пробелов.
Вы можете проверить свой код на странице проблемы , выбрав Python из выпадающего списка языков или загрузив .py
файл. Обратите внимание, что:
- Python 2.7 используется
- Ваш код должен быть полной программой, которая печатает
- Там нет ввода для этой проблемы, как колмогоров-сложности
- Ваша программа просто должна напечатать 400 значений, как указано, даже если она сломается на больших значениях
- Программы имеют 2 секунды для запуска
- Программы могут завершиться с ошибкой
- Вы можете использовать
exec
; «exec is denied» относится к оболочке exec
Ответы:
Это не то же самое решение, что и llhuii, но оно также имеет длину 42 байта.
Попробуйте онлайн!
Благодаря @JonathanFrech мы теперь на 40 байтах.
Попробуйте онлайн!
Есть еще один байт, который нужно сохранить, всего 39.
Попробуйте онлайн!
источник
print+n
, их решение должно отличаться от моего.-
знак, перемещаясь сprint~n
илиprint-n
и используя&
или~
, хотя я не получил ничего, чтобы работать. Кроме того,n=0;exec"print n;d=n^n+2;n^=d^-d%3;"*400
это красиво, но 40 байтов.print-n
кажется маловероятным, поскольку между установленными битамиn
и-n
.print~n
звучит более многообещающе в теории, но я не могу получить меньше 40 байт при таком подходе.Получение 39 байтов
Это объяснение того, как я получил 39-байтовое решение, которое Деннис и Джонатан Фрех также нашли отдельно. Или, скорее, это объясняет, как можно прийти к ответу задним числом, намного лучше, чем мой настоящий путь к нему, который был полон грязных рассуждений и тупиков.
Написание этого немного меньше в гольфе и с большим количеством паренов, это выглядит так:
Битовые соотношения
Мы начнем с идеи из моего 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
доk+1
.То есть, так как мы считаем
k=0,1,2,...
, нам просто нужно поддерживать текущий битовый паритет, а не вычислять его с нуля каждый раз. Обновление четности битовpar(k+1)^par(k)
- это четность количества бит, перевернутых при переходе отk
кk+1
, то есть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
Столбцы, вызывающие перенос, отмечены значком
*
. Они1
изменяют на a0
и передают бит переноса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)
какИспользуя
b
в качестве переменной, в которой мы сохраняем четность, это выглядит такНаписание кода
Собирая это вместе в коде, мы начинаем
k
и бит четностиb
как в0
, затем многократно печатаемn=2*k+b
и обновляемb=b^((k+1)^k)%3
иk=k+1
.46 байт
Попробуйте онлайн!
Мы убрали скобки вокруг
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/2+1^n/2
(помните, что это(n/2+1)^n/2
), переписавТак как
/2
удаляет последний бит, не имеет значения, делаем ли мы это до или после копирования. Итак, у нас естьn^=(n+2^n)/2%3
. Мы можем сохранить еще один байт, отметив , что по модулю3
,/2
эквивалентно*2
эквивалентно-
, отметив , чтоn+2^n
даже поэтому разделение актуально сращивание без напольного покрытия. Это даетn^=-(n+2^n)%3
41 байт
Попробуйте онлайн!
Наконец, мы можем объединить операции
n^=c;n+=2
вn=(n+2)^c
, гдеc
немного. Это работает, потому что^c
действует только на последний бит и+2
не заботится о последнем бите, поэтому операции коммутируют. Опять же, приоритет позволяет нам опускать парены и писатьn=n+2^c
.39 байт
Попробуйте онлайн!
источник
Это дает моё (xnor) 47-байтовое решение и мышление, которое привело меня к нему. Не читайте это, если вы хотите понять это самостоятельно.
Первая естественная идея - перебирать числа от 0 до 799, печатая только двоичные числа с четным числом 1.
52 байта
Попробуйте онлайн!
Здесь
~
биты дополняют бит, чтобы включитьeven<->odd
счетчик и дать истинное значение только на четных счетах.Мы можем улучшить этот метод, генерируя все значения вместо фильтрации. Обратите внимание, что выходные значения - это числа от 0 до 399, каждое из которых содержит бит, добавленный к четному числу 1 бит.
Итак,
n
номер th либо2*n+b
с либо,b=0
либоb=1
. Битb
можно найти, считая1
в битахn
и считая по модулю 2.49 байтов
Попробуйте онлайн!
Мы можем сократить 2 байта
2*
, выполнив итерацию0,2,4,...
, что не повлияет на число1
. Мы можем сделать это, используяexec
цикл, который выполняется в 400 раз и увеличиваяn
на 2 каждый цикл.47 байт
Попробуйте онлайн!
И это мое 47-байтовое решение. Я подозреваю, что большинство, если не все остальные 47-байтовые решения одинаковы.
источник
exec
Допустим ли ваш 47- байтовый длинный доступ?exec
а к командной строкеexec
.представление питона 3 от lhuii
Вот Python 3 представления для Evil Numbers на момент написания:
llhuii, вероятно, перенесли свой трюк на Python 3 и придумали решение, которое
Буквально портируя xnor 47B на Python 3, получаем 50B:
Я представил это как
ppcg(xnor)
. (Он добавляет круглые скобки вexec
иprint
, которые теперь являются функциями.) Он имеет статистику кода, отличную от других ответов Python 3, в которых есть некоторое количество пробелов. Интересно!Хотя есть более короткий способ переписать его (
exec
как правило, теряет свое конкурентное преимущество в Python 3):Это 49 байтов. Я представил это как
ppcg(xnor,alternative)
. Это имеет два байта пробела, как и ответ llhui! Это заставляет меня поверить, что ответ llhuii на Python 3 выглядит примерно так (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) Использование формулы для A001969
Вместо преобразования в двоичный код можно воспользоваться следующей формулой (из OEIS ):
Я очень плохо играю в гольф на Python, поэтому даже не собираюсь пробовать. Но вот быстрая попытка в JS.
NB: Я не думаю, что это будет правильная отправка JS, поскольку она просто заполняет массив, не отображая его. И даже в этом случае он на 5 байт длиннее, чем текущее лучшее решение JS (которое составляет 45 байт). Но дело тут не в этом.
Показать фрагмент кода
Надеюсь, это может дать некоторое вдохновение.
Использование массива, вероятно, не очень хорошая идея, потому что его нужно инициализировать и обновить. Вместо этого может быть более эффективным (с точки зрения размера кода) использовать рекурсивную функцию , которая объясняет, почему выигрышное решение отнимает больше времени, чем другие.
2) Построение последовательности Туэ-Морса с подстановками
Теоретически этот код должен работать:
Попробуйте онлайн! (работоспособная версия ограничена 20 терминами)
Он вычисляет последовательность Туэ-Морса с последовательными заменами и ищет положение 1 (злых чисел) в том же цикле.
Но:
3) Построение последовательности Туэ-Морса с побитовыми операциями
Начиная с прямого определения последовательности Туэ-Морса из Википедии , я пришел к этому алгоритму (переключаясь обратно на JS ... извините):
где мы отслеживаем текущее зло последовательности в e и используем 170 как битовую маску нечетных битов в байте.
источник
f=lambda n:_
for n in range(400):print f(n)
уже занимает 43 байта. Может быть, есть способ имитировать рекурсию, создавая массив, который ссылается на себя, или массив, который добавляет будущие элементы в конец.def
,for
,while
,lambda
(с параметром , по крайней мере), и т.д.while~0:print~1
не требует пробелов.((x=n++^n)^x/2)
кажется несколько многословным только для того, чтобы найти младший установленный бит. Весь этот беспорядок может быть заменен++n&-n
. Попробуйте онлайн!Подход с вложенными счетчиками
У меня есть идея для другого подхода, но я не достаточно опытен в игре в гольф на питоне, поэтому я оставлю это здесь, чтобы вы, ребята, рассмотрели в качестве еще одной возможной отправной точки для игры в гольф.
Нежелательная идея:
Попробуйте онлайн!
Глубина вложения девяти уровней, все петли одинаковы, поэтому, на мой взгляд, они должны быть построены
exec"something"*9+"deepest stuff"
. На практике я не знаю, возможно ли сделать что-то подобное с циклом for.Что нужно учитывать для игры в гольф:
может быть, есть какая-то другая возможность циклически повторяться дважды, кроме цикла for (я пробовал похожий на квинну подход, когда выполняемая строка дважды передавалась сама себе в качестве аргумента форматирования, но моя голова взорвалась).
может быть и лучшая альтернатива
if n<800:
, которая необходима здесь, потому что в противном случае мы будем печатать злые числа до 2 ^ 10источник
print
является оператором, а не функцией, и поэтому не может появляться внутри понимания.print '\n'.join([[[[[[[[[foo]foo]foo]foo]foo]foo]foo]foo]foo])
str.join
работает только со списками, содержащими строки, и дополнительные символы списка не должны быть напечатаны. Одно только форматирование заняло бы значительное количество байтов.Идея: более короткое соотношение
Требуется много символов,
bin(n).count('1')%2
чтобы вычислить четность счета битов. Возможно, арифметический способ короче, особенно учитывая ограниченную длину в битах.Милый способ такой же длины -
int(bin(n)[2:],3)%2
интерпретировать двоичное значение как основание3
(или любое нечетное основание). К сожалению, 4 байта тратятся на удаление0b
префикса. Это также работает, чтобы сделатьint(bin(n)[2:])%9%2
.Другая идея исходит от объединения битов с использованием xor. Если
n
имеет двоичное представлениеabcdefghi
, тоИтак,
r=n/16^n%16
зло тогда и только тогда, когдаn
оно зло. Затем можно повторить , что , какs=r/4^r%4
, значениеs
в0,1,2,3
, из которых1
и2
не являются злом, переключает с0<s<3
.52 байта
Попробуйте онлайн!
Это оказалось намного дольше. Есть много ручек, чтобы повернуть, как разделить число, как проверить окончательное число (возможно, справочную таблицу на основе битов). Я подозреваю, что они могут пойти так далеко, хотя.
источник
to_bytes
функцию целых чисел? Я сомневаюсь в этом, но кое-что нужно рассмотреть :)0b
:int(bin(n),13)%2
! : Dn=0;exec"print~int(bin(n),13)%2+n;n+=2;"*400
По построению,
n+n^n
это всегда зло, но мои плохие навыки Python могли предложить только 61-байтовое решение:Спасибо @Peilonrayz за сохранение 5 байтов и @ Mr.Xcoder за сохранение 1 байта:
источник
for n in sorted(n^n*2for n in range(512))[:400]:print n
.n+n^n
так же, какn^n*2
Идея: A006068 («a (n) серым n»)
Идея Нейла сортировать все
2n XOR n
заинтриговала меня, поэтому я попытался найти показатели такого рода. Я написал этот код , и он показывает, что мы можем написать что-то вроде этого:Где
a(n)
A006068 (н).Попробуйте онлайн!Тем не менее, это предполагает, что у нас есть небольшой способ вычислить A006068. Это уже 38 байтов, при условии, что мы можем вычислить его в 4 байта (
a(n)
часть). Реальная реализация (в заголовке TIO) намного длиннее. Думаю, не так много надежды на это.источник
Идея: уменьшить по XOR
Если вы XOR все биты
n
вместе, это будет0
для зла и1
не зла. Вы можете сделать это с помощью рекурсивной функции (которая может занять больше времени?), Например так:Это возвращает 1 за зло.
Это 35 байтов, и проверяет, является ли число злым. К сожалению,
filter
уже 6 байтов, так что это не было оптимальным дословным решением, но эта идея, вероятно, может быть реализована.источник
f=lambda n:n>1and f(n/2^n&1)or-~-n
за -1 байт.f(n/2^n&1)
возвращается 0 ...Метод подстановки: {1 -> {1, -1}, -1 -> {-1, 1}}
Вы также можете сделать эту замену 10 раз {1 -> {1, -1}, -1 -> {-1, 1}}, затем сгладить и проверить позиции 1
вот код математики
источник