GNU Prolog, 98 byte
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).
Questa risposta è un ottimo esempio di come Prolog può lottare anche con i formati I / O più semplici. Funziona nel vero stile Prolog descrivendo il problema, piuttosto che l'algoritmo per risolverlo: specifica ciò che conta come una disposizione legale della bolla, chiede a Prolog di generare tutte quelle disposizioni della bolla, e quindi conta. La generazione richiede 55 caratteri (le prime due righe del programma). Il conteggio e l'I / O prendono le altre 43 (la terza riga e la nuova riga che separa le due parti). Scommetto che questo non è un problema che l'OP si aspettava che le lingue dovessero lottare con l'I / O! (Nota: l'evidenziazione della sintassi di Stack Exchange rende questa lettura più difficile, non più semplice, quindi l'ho disattivata).
Spiegazione
Iniziamo con una versione pseudocodice di un programma simile che in realtà non funziona:
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).
Dovrebbe essere abbastanza chiaro come b
funziona: stiamo rappresentando le bolle tramite elenchi ordinati (che sono una semplice implementazione di più insiemi che fa sì che uguali più insiemi siano confrontati uguali) e una singola bolla []
ha un conteggio di 1, con una bolla più grande con un conteggio uguale al conteggio totale delle bolle all'interno più 1. Per un conteggio di 4, questo programma (se funzionasse) genererebbe i seguenti elenchi:
[[],[],[],[]]
[[],[],[[]]]
[[],[[],[]]]
[[],[[[]]]]
[[[]],[[]]]
[[[],[],[]]]
[[[],[[]]]]
[[[[],[]]]]
[[[[[]]]]]
Questo programma non è adatto come risposta per diversi motivi, ma il più urgente è che Prolog in realtà non ha un map
predicato (e scriverlo richiederebbe troppi byte). Quindi, invece, scriviamo il programma in questo modo:
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).
L'altro problema principale qui è che andrà in un ciclo infinito quando eseguito, a causa del modo in cui funziona l'ordine di valutazione di Prolog. Tuttavia, possiamo risolvere il ciclo infinito riorganizzando leggermente il programma:
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).
Questo potrebbe sembrare abbastanza strano - stiamo sommando i conteggi prima di sapere cosa sono - ma GNU Prolog #=
è in grado di gestire quel tipo di aritmetica non causale, e perché è la prima riga di b
, e HeadCount
e TailCount
deve essere inferiore a Count
(che è noto), serve come metodo per limitare naturalmente quante volte il termine ricorsivo può corrispondere, e quindi fa sì che il programma termini sempre.
Il prossimo passo è giocare un po 'verso il basso. Rimozione di spazi bianchi, utilizzo di nomi di variabili a carattere singolo, utilizzo di abbreviazioni come :-
for if
e ,
for and
, utilizzo setof
piuttosto che listof
(ha un nome più corto e produce gli stessi risultati in questo caso) e utilizzo sort0(X,X)
piuttosto che is_sorted(X)
(perché is_sorted
non è in realtà una funzione reale, L'ho inventato):
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).
Questo è abbastanza breve, ma è possibile fare di meglio. L'intuizione chiave è che [H|T]
è davvero prolisso man mano che vanno le sintassi dell'elenco. Come i programmatori di Lisp sapranno, un elenco è fondamentalmente solo composto da celle contro, che sono fondamentalmente solo tuple, e quasi nessuna parte di questo programma utilizza i builtin dell'elenco. Prolog ha diverse sintassi di tupla molto brevi (la mia preferita è A-B
, ma la mia seconda preferita è A/B
, che sto usando qui perché produce un output di debug più facile da leggere in questo caso); e possiamo anche scegliere il nostro carattere singolo nil
per la fine della lista, piuttosto che rimanere bloccati con il carattere due []
(ho scelto x
, ma praticamente tutto funziona). Quindi, invece di [H|T]
, possiamo usare T/H
e ottenere output dab
assomiglia a questo (nota che l'ordinamento su tuple è leggermente diverso da quello sugli elenchi, quindi questi non sono nello stesso ordine di sopra):
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)))
Questo è piuttosto difficile da leggere rispetto agli elenchi nidificati sopra, ma è possibile; salta mentalmente la x
s e interpretala /()
come una bolla (o semplicemente /
come una bolla degenerata senza contenuto, se non c'è ()
dopo) e gli elementi hanno una corrispondenza 1: 1 (se disordinata) con la versione dell'elenco mostrata sopra .
Naturalmente, questa rappresentazione dell'elenco, nonostante sia molto più breve, presenta un grosso svantaggio; non è integrato nella lingua, quindi non possiamo usare sort0
per verificare se il nostro elenco è ordinato. sort0
è comunque abbastanza prolisso, quindi farlo a mano non è una perdita enorme (in effetti, farlo a mano nella [H|T]
rappresentazione dell'elenco arriva esattamente allo stesso numero di byte). L'intuizione chiave qui è che il programma come scritto controlla per vedere se l'elenco è ordinato, se la sua coda è ordinata, se la sua coda è ordinata e così via; ci sono molti controlli ridondanti e possiamo sfruttarlo. Invece, verificheremo solo che i primi due elementi siano in ordine (il che assicura che l'elenco finirà per essere ordinato una volta che l'elenco stesso e tutti i suoi suffissi sono stati controllati).
Il primo elemento è facilmente accessibile; questo è solo il capo della lista H
. Il secondo elemento è piuttosto difficile da accedere, tuttavia, e potrebbe non esistere. Fortunatamente, x
è meno di tutte le tuple che stiamo prendendo in considerazione (tramite l'operatore di confronto generalizzato di Prolog @>=
), quindi possiamo considerare il "secondo elemento" di una lista singleton x
e il programma funzionerà bene. Per quanto riguarda effettivamente l'accesso al secondo elemento, il metodo più difficile è quello di aggiungere un terzo argomento (un argomento out) a b
, che ritorna x
nel caso base e H
nel caso ricorsivo; questo significa che possiamo afferrare la testa della coda come output dalla seconda chiamata ricorsiva a B
, e naturalmente la testa della coda è il secondo elemento della lista. Quindi b
sembra così ora:
b(x,0,x).
b(T/H,N,H):-N#=A+B+1,b(H,A,_),b(T,B,J),H@>=J.
Il caso base è abbastanza semplice (elenco vuoto, restituisce un conteggio di 0, il "primo elemento" dell'elenco vuoto è x
). Il caso ricorsivo inizia allo stesso modo di prima (solo con la T/H
notazione anziché [H|T]
, e H
come argomento extra); ignoriamo l'argomento aggiuntivo dalla chiamata ricorsiva sulla testa, ma lo memorizziamo nella J
chiamata ricorsiva sulla coda. Quindi tutto ciò che dobbiamo fare è accertarci che H
sia maggiore o uguale a J
(ovvero "se l'elenco ha almeno due elementi, il primo è maggiore o uguale al secondo) per garantire che l'elenco venga ordinato.
Sfortunatamente, si setof
adatta se si tenta di utilizzare la definizione precedente di c
insieme a questa nuova definizione di b
, poiché tratta i parametri non utilizzati più o meno allo stesso modo di un SQL GROUP BY
, che non è completamente quello che vogliamo. È possibile riconfigurarlo per fare ciò che vogliamo, ma quella riconfigurazione costa caratteri. Invece, usiamo findall
, che ha un comportamento predefinito più conveniente ed è più lungo di soli due caratteri, dandoci questa definizione di c
:
c(X,Y):-findall(A,b(A,X,_),L),length(L,Y).
E questo è il programma completo; generare in tergo modelli di bolle, quindi spendere un intero carico di byte contandoli (abbiamo bisogno di un tempo piuttosto lungo findall
per convertire il generatore in un elenco, quindi sfortunatamente un nome verbalmente length
per controllare la lunghezza di tale elenco, oltre alla piastra di caldaia per una dichiarazione di funzione).