Обратите внимание, вызов скопирован из вопроса, заданного на math.stackexchange .
Недавно я приобрел некоторые навыки выдувания пузырей. Сначала я бы пускал пузыри вот так:
Но потом все стало странным:
Через некоторое время я начал пускать довольно странные пузыри:
Выдув сотни, может быть, даже тысячи таких пузырьков, мой лоб внезапно сморщился от вопроса: учитывая n пузырьков, сколько разных способов вы можете их расположить? Например, если n = 1, существует только 1 расположение. Если n = 2, есть 2 расположения. Если n = 3, есть 4 договоренности. Если n = 4, есть 9 договоренностей.
Вот 9 аранжировок из 4 пузырьков:
После того, как я взорвал все эти чудесные пузыри, я решил, что должен поделиться радостью, посчитав их договоренности с вами. Итак, вот ваша задача:
Цель
Напишите программу, функцию или подобное, которая подсчитывает количество способов, которыми вы можете расположить n
пузырьки.
вход
n
количество пузырьков. n> 0
Выход
Количество способов, которыми вы можете расположить эти пузыри.
Критерии победы
Было бы очень здорово, если бы мы взорвали ваш код. Чем меньше вы делаете свой код, тем легче будет это сделать. Таким образом, тот, кто создаст код с наименьшим количеством байтов, выиграет конкурс.
Дополнительная информация
источник
0
действительным вход?Ответы:
Python 2,
9287 байтНа простом английском языке: для вычисления
a(n)
мы вычисляемd*a(d)*a(n-k)
для каждого делителяd
каждого положительного целого числа,k
меньшего или равногоn
, суммируем все это и делим наn-1
.Для того, чтобы сделать его работать быстрее, работать в Python 3 (замена
/
с//
в приведенной выше функции) и memoize:Если вы сделаете это, он вычисляется
a(50) = 425976989835141038353
мгновенно.источник
lru_cache()
запоминает функцию?lru_cache
.True
дляn<2
. Я думаю, это нормальноn=1
, поскольку в PythonTrue
в числовом контексте оценивается как 1, ноa(0)
должно возвращать 0. Вы можете исправить это с помощью,n<2 and n or sum...
но может быть более компактный способ.n
то мы можем смело игнорировать этот угловой случай, так как он не влияет на рекурсивные вызовы для более высокогоn
.GNU Prolog, 98 байт
Этот ответ является отличным примером того, как Prolog может бороться даже с простейшими форматами ввода-вывода. Он работает в истинном стиле Пролог через описание проблемы, а не алгоритм для ее решения: он определяет, что считается допустимым расположением пузырьков, просит Пролог генерировать все эти расположения пузырьков, а затем подсчитывает их. Генерация занимает 55 символов (первые две строчки программы). Подсчет и ввод / вывод занимают остальные 43 (третья строка и новая строка, разделяющая две части). Могу поспорить, что это не проблема, которую OP ожидал, что языки будут бороться с I / O! (Примечание: подсветка синтаксиса в стеке Exchange затрудняет чтение, а не упрощает, поэтому я отключил его).
объяснение
Давайте начнем с псевдокодовой версии аналогичной программы, которая на самом деле не работает:
Должно быть достаточно ясно, как
b
работает: мы представляем пузырьки через отсортированные списки (которые представляют собой простую реализацию мультимножеств, которая заставляет равные мультимножества сравнивать равные), и один пузырь[]
имеет счетчик 1, а большой пузырь имеет счетчик равно общему количеству пузырьков внутри плюс 1. Для счетчика 4 эта программа (если бы она работала) генерировала следующие списки:Эта программа непригодна в качестве ответа по нескольким причинам, но наиболее неотложной является то, что у Пролога на самом деле нет
map
предиката (и написание этого заняло бы слишком много байтов). Так что вместо этого мы пишем программу более похожую на эту:Другая важная проблема заключается в том, что при запуске он входит в бесконечный цикл из-за того, как работает порядок оценки Пролога. Тем не менее, мы можем решить бесконечный цикл, слегка переставив программу:
Это может выглядеть довольно странно - мы суммируем значения до того, как узнаем, что они есть, но GNU Prolog
#=
способен обрабатывать такие некаузальные арифметики, и потому что это самая первая строкаb
,HeadCount
иTailCount
оба должны быть меньше чемCount
(что известно), он служит способом естественного ограничения количества совпадений рекурсивного члена и, таким образом, приводит к тому, что программа всегда завершается.Следующим шагом будет немного поиграть в гольф. Удаление пробелов, использование односимвольных имен переменных, использование аббревиатур, таких как
:-
forif
и,
forand
, использованиеsetof
вместоlistof
(у него более короткое имя и в этом случае такие же результаты) и использованиеsort0(X,X)
вместоis_sorted(X)
(потому что наis_sorted
самом деле не является реальной функцией, Я сделал это)Это довольно коротко, но это возможно сделать лучше. Ключевое понимание заключается в том, что
[H|T]
в действительности синтаксисы списков действительно многословны. Как знают программисты на Лиспе, список в основном состоит из cons-ячеек, которые в основном являются просто кортежами, и вряд ли какая-либо часть этой программы использует встроенные списки. У Пролога есть несколько очень коротких синтаксисов кортежей (мой любимыйA-B
, но мой второй любимыйA/B
, который я использую здесь, потому что в этом случае он дает более легкий для чтения отладочный вывод); и мы можем также выбрать наш собственный односимвольный символnil
в конце списка, вместо того, чтобы застрять с двухсимвольным символом[]
(я выбралx
, но в основном все работает). Таким образом, вместо[H|T]
, мы можем использоватьT/H
, и получить вывод изb
это выглядит так (обратите внимание, что порядок сортировки в кортежах немного отличается от порядка в списках, поэтому они не находятся в том же порядке, что и выше):Это довольно трудно читать, чем вложенные списки выше, но это возможно; мысленно пропустите
x
s и интерпретируйте/()
как пузырь (или просто/
как вырожденный пузырь без содержимого, если нет()
после него), и элементы имеют соответствие 1-к-1 (если неупорядочено) с версией списка, показанной выше ,Конечно, это представление списка, несмотря на то, что оно намного короче, имеет большой недостаток; он не встроен в язык, поэтому мы не можем
sort0
проверить, отсортирован ли наш список.sort0
В любом случае это довольно многословно, поэтому выполнение этого вручную не является огромной потерей (фактически, выполнение этого вручную в[H|T]
представлении списка приводит к одинаковому количеству байтов). Ключевым моментом здесь является то, что программа в письменном виде проверяет, отсортирован ли список, отсортирован ли его хвост, отсортирован ли его хвост и т. Д .; Есть много избыточных проверок, и мы можем использовать это. Вместо этого мы просто проверим, чтобы первые два элемента были в порядке (что гарантирует, что список будет отсортирован, как только сам список и все его суффиксы проверены).Первый элемент легко доступен; это только глава списка
H
. Второй элемент довольно сложен для доступа, и может не существовать. К счастью,x
это меньше, чем все рассматриваемые нами кортежи (через обобщенный оператор сравнения Prolog@>=
), поэтому мы можем считать «вторым элементом» одноэлементного спискаx
и программа будет работать нормально. Что касается фактического доступа ко второму элементу, самый краткий метод состоит в добавлении третьего аргумента (аргумента out)b
, который возвращаетсяx
в базовом иH
рекурсивном случаях; это означает, что мы можем получить головку хвоста как результат второго рекурсивного вызоваB
, и, конечно, голова хвоста является вторым элементом списка. Такb
выглядит сейчас:Базовый случай достаточно прост (пустой список, возвращает счетчик 0, «первый элемент» пустого списка
x
). Рекурсивный случай начинается так же, как и раньше (только сT/H
нотации , а не[H|T]
, и вH
качестве дополнительного аргумента из); мы игнорируем дополнительный аргумент из рекурсивного вызова на голове, но сохраняем его вJ
рекурсивном вызове на хвосте. Затем все, что нам нужно сделать, - это убедиться, чтоH
оно больше или равноJ
(т. Е. «Если в списке есть хотя бы два элемента, первый больше или равен второму), чтобы гарантировать, что список будет отсортирован.К сожалению,
setof
он подходит, если мы пытаемся использовать предыдущее определениеc
вместе с этим новым определениемb
, потому что оно обрабатывает неиспользуемые параметры более или менее так же, как SQLGROUP BY
, что совсем не то, что мы хотим. Можно перенастроить его на то, что мы хотим, но это перенастройка стоит символов. Вместо этого мы используемfindall
, который имеет более удобное поведение по умолчанию и длиннее всего на два символа, давая нам следующее определениеc
:И это полная программа; кратко генерируйте шаблоны пузырьков, затем тратите целую массу байтов, считая их (нам нужно довольно много времени,
findall
чтобы преобразовать генератор в список, а затем, к сожалению, многословно назвать его,length
чтобы проверить длину этого списка, плюс шаблон для объявления функции).источник
maplist/2-8
предикат , хотя я не уверен, что это сделает ситуацию короче.| ?- maplist(reverse,[A,B]). uncaught exception: error(existence_error(procedure,maplist/2),top_level/0)
maplist
является очень часто используемым предикатом, который предоставляется в основных дистрибутивах Пролога (таких как SWI-Prolog и SiCStus)Mathematica, 68 байт
Бьюсь об заклад, это может быть побеждено (даже в Mathematica) с нуля, но вот встроенная версия:
ButcherTreeCount
имеет индекс 0, отсюда[#+1]
и возвращает список всех значений вплоть до аргумента, отсюдаLast@
. Но в остальном это просто встроенная функция для этой функции. Однако, это требует загрузки пакета, что и делает первая строка.источник