Впорядкування бульбашок


26

Зауважте, виклик, скопійований із запитання, заданого на math.stackexchange .

Нещодавно я здобув досить певну майстерність у видуванні бульбашок. Спочатку я б надував такі бульбашки: введіть тут опис зображення

Але потім все стало дивно:

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

Через деякий час я видував досить дивні бульбашки:

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

Після видування сотень, може, навіть тисяч таких бульбашок мій лоб раптом зморщився запитанням: Дано п ять бульбашок, скільки різних способів ви можете їх влаштувати? Наприклад, якщо n = 1, існує лише 1 розташування. Якщо n = 2, є 2 домовленості. Якщо n = 3, є 4 домовленості. Якщо n = 4, існує 9 домовленостей.

Ось 9 композицій із 4 бульбашок:
введіть тут опис зображення введіть тут опис зображення введіть тут опис зображення введіть тут опис зображення введіть тут опис зображення введіть тут опис зображення введіть тут опис зображення введіть тут опис зображення введіть тут опис зображення

Після того, як видули всі ці дивовижні бульбашки, я вирішив, що мені слід поділитися із задоволенням від того, щоб порахувати їхні домовленості з вами. Отже ось ваше завдання:


Мета

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


Вхідні дані

n, кількість бульбашок. n> 0


Вихідні дані

Кількість способів розташування цих бульбашок.


Критерії виграшу

Було б дуже здорово, якщо ми можемо роздути міхур навколо вашого коду. Чим менше ви зробите свій код, тим простіше буде це зробити. Так людина, яка зробить код з найменшою кількістю байтів, виграє конкурс.


Додаткова інформація

OEIS


5
Якщо бульбашки можуть перетинатися, це відкрита проблема , із 173 розв’язками для n = 4 .
orlp

@orlp На щастя, ці бульбашки не перетинаються.
TheNumberOne

1
Чи 0дійсний вхід?
Мартін Ендер

@ KenY-N Так. Внизу вже є посилання OEIS
Роман Греф

На жаль! Видалити час дурного коментаря ...
Ken YN

Відповіді:


12

Python 2, 92 87 байт

a=lambda n:n<2or sum((k%d<1)*d*a(d)*a(n-k)for k in range(1,n)for d in range(1,1+k))/~-n

Простий англійською мовою: для обчислення a(n)ми обчислюємо d*a(d)*a(n-k)для кожного дільника dкожне додатне ціле число, kменше або рівне n, підсумовуємо всі ці значення і ділимо на n-1.

Для того, щоб зробити його працювати швидше, працювати в Python 3 (заміна /з //в наведеній вище функції) і memoize:

import functools
a = functools.lru_cache(None)(a)

Якщо ви це зробите, це обчислюється a(50) = 425976989835141038353миттєво.


Ого, це круто. Я припускаю, що lru_cache()запам'ятовує функцію?
Патрік Робертс

@PatrickRoberts Так, зазвичай він використовується як декоратор функції, але ви також можете застосувати його вручну до функції.
orlp

@PatrickRoberts Ось документи дляlru_cache .
PM 2Ring

Ця функція повертається Trueдля n<2. Я думаю, що це нормально n=1, оскільки в Python Trueоцінюється до 1 в числових контекстах, але a(0)слід повернути 0. Ви можете це виправити, n<2 and n or sum...але може бути і більш компактний спосіб.
PM 2Ring

Я думаю, можна зробити аргумент про те, що існує один із способів розташування нульових бульбашок, але це не відповідає A000081. ОТОХ, якщо нам потрібно вирішити лише позитивне рішення, nми можемо сміливо ігнорувати цей кутовий випадок, оскільки це не впливає на рекурсивні виклики до вищих n.
PM 2Ring

10

GNU Prolog, 98 байт

b(x,0,x).
b(T/H,N,H):-N#=A+B+1,b(H,A,_),b(T,B,J),H@>=J.
c(X,Y):-findall(A,b(A,X,_),L),length(L,Y).

Ця відповідь є чудовим прикладом того, як Prolog може боротися навіть з найпростішими форматами вводу / виводу. Він працює в справжньому стилі Prolog, описуючи проблему, а не алгоритм її вирішення: він визначає, що вважається законним розташуванням бульбашок, просить Prolog генерувати всі ці компонування міхурів, а потім підраховує їх. Покоління займає 55 символів (перші два рядки програми). Підрахунок і введення / виведення приймають інші 43 (третій рядок і новий рядок, який розділяє дві частини). Гадаю, це не проблема, що ОП, як очікується, спричинить боротьбу мов із введенням / виводу! (Примітка. Підсвічування синтаксису Stack Exchange робить це важче для читання, а не простіше, тому я його вимкнув).

Пояснення

Почнемо з версії псевдокоду подібної програми, яка насправді не працює:

b(Bubbles,Count) if map(b,Bubbles,BubbleCounts)
                and sum(BubbleCounts,InteriorCount)
                and Count is InteriorCount + 1
                and is_sorted(Bubbles).
c(Count,NPossibilities) if listof(Bubbles,b(Bubbles,Count),List)
                       and length(List,NPossibilities).

Це має бути досить зрозуміло, як це bпрацює: ми представляємо бульбашки за допомогою відсортованих списків (що є простою реалізацією мультисетів, що змушує рівних мультисетів порівняти рівними), а один міхур []має кількість 1, а більший міхур має кількість дорівнює загальній кількості бульбашок всередині плюс 1. Для підрахунку 4 ця програма (якщо вона працювала) генерує такі списки:

[[],[],[],[]]
[[],[],[[]]]
[[],[[],[]]]
[[],[[[]]]]
[[[]],[[]]]
[[[],[],[]]]
[[[],[[]]]]
[[[[],[]]]]
[[[[[]]]]]

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

b([], 0).
b([Head|Tail],Count) if b(Head,HeadCount)
                    and b(Tail,TailCount)
                    and Count is HeadCount + TailCount + 1
                    and is_sorted([Head|Tail]).
c(Count,NPossibilities) if listof(Bubbles,b(Bubbles,Count),List)
                       and length(List,NPossibilities).

Інша основна проблема тут полягає в тому, що він перейде в нескінченний цикл під час запуску через те, як працює порядок оцінки Prolog. Однак ми можемо вирішити нескінченний цикл, трохи переставивши програму:

b([], 0).
b([Head|Tail],Count) if Count #= HeadCount + TailCount + 1
                    and b(Head,HeadCount)
                    and b(Tail,TailCount)
                    and is_sorted([Head|Tail]).
c(Count,NPossibilities) if listof(Bubbles,b(Bubbles,Count),List)
                       and length(List,NPossibilities).

Це може виглядати досить дивно - ми додаємо підрахунки, перш ніж ми дізнаємося, що вони є, - але GNU Prolog #=здатний поводитись з такою різновидом арифметики, і тому що це перший рядок b, і той, HeadCountі інший TailCountповинен бути менше Count(що відомо), він служить методом природного обмеження, скільки разів може відповідати рекурсивний термін, і, таким чином, змушує програму завжди припинятися.

Наступний крок - трохи полегшити це. Видалення символів пробілів, використовуючи імена змінних з одного символу, використовуючи скорочення , як :-для ifі ,для and, використовуючи setofзамість listof(він має більш коротку назву і виробляє ті ж результати , в даному випадку), і використовуючи sort0(X,X)замість is_sorted(X)(бо is_sortedце на самому ділі не справжня функції, Я склав це):

b([],0).
b([H|T],N):-N#=A+B+1,b(H,A),b(T,B),sort0([H|T],[H|T]).
c(X,Y):-setof(A,b(A,X),L),length(L,Y).

Це досить коротко, але можна зробити і краще. Ключове розуміння полягає в тому, що [H|T]це насправді багатослівний під час переліку синтаксисів списку. Як знають програмісти Lisp, список в основному складається з комірок мінусів, які в основному є лише кортежами, і навряд чи будь-яка частина цієї програми використовує вбудовані списки. Prolog має кілька дуже коротких кортежних синтаксисів (мій улюблений A-B, але мій другий улюблений - A/Bя використовую тут, оскільки він дає простіший для читання вихід налагодження в цьому випадку); і ми також можемо вибрати власний односимвольний nilдля кінця списку, а не застрягти з двома символами [](я вибрав x, але в основному все працює). Тож замість цього [H|T]ми можемо використовувати T/Hта отримувати вихідb це виглядає приблизно так (зауважте, що порядок сортування кортежів трохи відрізняється від порядку в списках, тому вони не в тому ж порядку, як вище):

x/x/x/x/x
x/x/x/(x/x)
x/(x/x)/(x/x)
x/x/(x/x/x)
x/(x/x/x/x)
x/x/(x/(x/x))
x/(x/x/(x/x))
x/(x/(x/x/x))
x/(x/(x/(x/x)))

Це читати досить важче, ніж вкладені списки вище, але це можливо; подумки пропустити xs і інтерпретувати /()як міхур (або просто звичайний /як вироджений міхур без вмісту, якщо ()після нього немає ), а елементи мають відповідність 1-до-1 (якщо не впорядковано) із наведеною вище списковою версією .

Звичайно, подання цього списку, незважаючи на те, що воно значно скорочене, має великий недолік; він не вбудований у мову, тому ми не можемо використовувати, sort0щоб перевірити, чи відсортований наш список. sort0все-таки є досить багатослівним, однак, це робити вручну не є величезною втратою (насправді, це робити вручну в [H|T]представленні списку доходить приблизно до такої ж кількості байтів). Основне розуміння тут полягає в тому, що програма, як письмові перевірки, перевіряє, чи сортується список, чи сортується його хвіст, чи сортується його хвіст тощо; є багато зайвих перевірок, і ми можемо це використати. Натомість ми просто перевіримо, щоб перші два елементи були в порядку (що гарантує, що список буде впорядкований після того, як перевіряється сам список та всі його суфікси).

Перший елемент легко доступний; це лише голова списку H. Другий елемент доступний, проте, важче, але може і не існувати. На щастя, xце менше, ніж усі кортежі, які ми розглядаємо (через узагальнений оператор порівняння Prolog @>=), тому ми можемо вважати "другим елементом" списку синглтон xі програма буде працювати нормально. Що стосується фактичного доступу до другого елемента, терсест-метод полягає в додаванні до третього аргументу (аргументу з виходу) b, який повертається xв базовому випадку і Hв рекурсивному випадку; це означає, що ми можемо схопити голову за хвіст як вихід з другого рекурсивного дзвінка до B, і, звичайно, голова хвоста є другим елементом списку. Так bвиглядає ось так:

b(x,0,x).
b(T/H,N,H):-N#=A+B+1,b(H,A,_),b(T,B,J),H@>=J.

Базовий випадок досить простий (порожній список, повернути кількість 0, "перший елемент" порожнього списку x). Рекурсивний випадок починається так само, як і раніше (тільки з T/Hпозначеннями, а не як [H|T]і Hяк додатковий аргумент); ми нехтуємо додатковим аргументом від рекурсивного дзвінка на голові, але зберігаємо його в Jрекурсивному дзвінку на хвості. Тоді все, що нам потрібно зробити, це переконатися, що Hвоно більше або дорівнює J(тобто "якщо у списку принаймні два елементи, перший більший або дорівнює другому), щоб переконатися, що список закінчується впорядкованим.

На жаль, setofпридатний, якщо ми намагаємось використати попереднє визначення cразом із цим новим визначенням b, оскільки воно розглядає невикористані параметри приблизно так само, як і SQL GROUP BY, що абсолютно не те, що ми хочемо. Можна налаштувати його так, щоб ми хотіли, але ця конфігурація коштує символів. Натомість ми використовуємо findall, яке має більш зручну поведінку за замовчуванням і лише на два символи довше, що дає нам таке визначення c:

c(X,Y):-findall(A,b(A,X,_),L),length(L,Y).

І ось повна програма; findallпоступово генеруйте шаблони бульбашок, потім витрачайте цілий набір байтів, рахуючи їх (нам потрібно досить довге для перетворення генератора в список, потім, на жаль, багатослівне ім'я, lengthщоб перевірити довжину цього списку, а також панель котла для оголошення функції).


"Пролог насправді не має предиката карти" : у Prolog є maplist/2-8предикат , хоча я не впевнений, що це скоротить тут речі.
Фаталізувати

@Fatalize: Так, схоже, це було додано в новій версії. Це не в документації для встановлення, яку я маю, і це не працює на практиці:| ?- maplist(reverse,[A,B]). uncaught exception: error(existence_error(procedure,maplist/2),top_level/0)

Це справді дивно; maplistє дуже часто використовуваним предикатом, який надається в основних дистрибутивах Prolog (наприклад, SWI-Prolog і SiCStus)
Fatalize

10

Математика, 68 байт

Б'юсь об заклад, що це може бути переможене (навіть у Mathematica) з реальною реалізацією, але ось вбудована версія:

<<NumericalDifferentialEquationAnalysis`
Last@ButcherTreeCount[#+1]&

ButcherTreeCountє 0-індексованим, отже [#+1], і повертає список усіх значень до його аргументу, звідси і Last@. Але в іншому випадку це лише вбудований для цієї функції. Однак для цього потрібно завантажити пакет, що і робить перший рядок.


8
"Звичайно, Mathematica має для цього вбудований".
orlp
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.