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)))
Це читати досить важче, ніж вкладені списки вище, але це можливо; подумки пропустити x
s і інтерпретувати /()
як міхур (або просто звичайний /
як вироджений міхур без вмісту, якщо ()
після нього немає ), а елементи мають відповідність 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
щоб перевірити довжину цього списку, а також панель котла для оголошення функції).