GNU Prolog, 98 bytes
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).
Esta resposta é um ótimo exemplo de como o Prolog pode enfrentar até os formatos de E / S mais simples. Ele funciona no verdadeiro estilo Prolog, descrevendo o problema, em vez do algoritmo para resolvê-lo: especifica o que conta como um arranjo legal de bolhas, pede ao Prolog para gerar todos esses arranjos e depois os conta. A geração ocupa 55 caracteres (as duas primeiras linhas do programa). A contagem e a E / S levam as outras 43 (a terceira linha e a nova linha que separa as duas partes). Aposto que esse não é um problema que o OP esperava que os idiomas lutassem com a E / S! (Nota: o destaque da sintaxe do Stack Exchange torna isso mais difícil de ler, não mais fácil, então eu o desativei).
Explicação
Vamos começar com uma versão em pseudocódigo de um programa semelhante que realmente não funciona:
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).
Deve ficar bem claro como b
funciona: estamos representando bolhas por meio de listas classificadas (que são uma implementação simples de multisets que faz com que multisets iguais sejam comparados com iguais) e um único balão []
conta 1, com um balão maior contando igual à contagem total de bolhas dentro de mais 1. Para uma contagem de 4, este programa (se funcionasse) geraria as seguintes listas:
[[],[],[],[]]
[[],[],[[]]]
[[],[[],[]]]
[[],[[[]]]]
[[[]],[[]]]
[[[],[],[]]]
[[[],[[]]]]
[[[[],[]]]]
[[[[[]]]]]
Este programa é inadequado como resposta por vários motivos, mas o mais premente é que o Prolog não possui um map
predicado (e escrever isso levaria muitos bytes). Então, em vez disso, escrevemos o programa mais ou menos assim:
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).
O outro grande problema aqui é que ele entrará em um loop infinito quando executado, devido à maneira como a ordem de avaliação do Prolog funciona. No entanto, podemos resolver o loop infinito reorganizando o programa levemente:
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).
Isso pode parecer bastante estranho - estamos adicionando as contagens antes de sabermos o que elas são - mas o GNU Prolog #=
é capaz de lidar com esse tipo de aritmética não-causal, e porque é a primeira linha de b
, e a HeadCount
e TailCount
deve ser menor que Count
(que é conhecido), serve como um método para limitar naturalmente quantas vezes o termo recursivo pode corresponder e, portanto, faz com que o programa seja sempre encerrado.
O próximo passo é jogar um pouco de golfe. Removendo o espaço em branco, usando nomes de variáveis de um caractere, usando abreviações como :-
for if
e ,
for and
, usando em setof
vez de listof
(ele tem um nome mais curto e produz os mesmos resultados nesse caso) e usando em sort0(X,X)
vez de is_sorted(X)
(porque is_sorted
na verdade não é uma função real, Eu inventei):
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).
Isso é bastante curto, mas é possível fazer melhor. O principal insight é que [H|T]
é realmente detalhado conforme as sintaxes da lista. Como os programadores do Lisp saberão, uma lista é basicamente feita apenas de células contras, que são basicamente apenas tuplas, e quase nenhuma parte deste programa está usando os recursos da lista. O Prolog possui várias sintaxes de tupla muito curtas (a minha favorita é A-B
a segunda preferida A/B
, que estou usando aqui porque, nesse caso, produz saída de depuração mais fácil de ler); e também podemos escolher nosso próprio caractere único nil
para o final da lista, em vez de ficarmos presos ao caractere de dois caracteres []
(eu escolhi x
, mas basicamente tudo funciona). Então, em vez de [H|T]
, podemos usar T/H
e obter saída deb
que se parece com isso (observe que a ordem de classificação nas tuplas é um pouco diferente da das listas, portanto, elas não estão na mesma ordem acima):
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)))
É um pouco mais difícil de ler do que as listas aninhadas acima, mas é possível; pule mentalmente x
s e interprete /()
como uma bolha (ou simplesmente /
como uma bolha degenerada sem conteúdo, se não houver ()
depois dela), e os elementos têm uma correspondência 1 para 1 (se desordenada) com a versão da lista mostrada acima .
Obviamente, essa representação da lista, apesar de muito mais curta, tem uma grande desvantagem; não está embutido no idioma; portanto, não podemos usar sort0
para verificar se nossa lista está classificada. sort0
de qualquer maneira, é bastante detalhado, portanto, fazê-lo manualmente não é uma perda enorme (de fato, fazê-lo manualmente na [H|T]
representação da lista chega exatamente ao mesmo número de bytes). O principal insight aqui é que o programa, como escrito, verifica se a lista está classificada, se a cauda está classificada, se a cauda está classificada, e assim por diante; existem muitas verificações redundantes e podemos explorar isso. Em vez disso, apenas verificaremos se os dois primeiros elementos estão em ordem (o que garante que a lista seja classificada assim que a lista em si e todos os seus sufixos forem verificados).
O primeiro elemento é facilmente acessível; isso é apenas o chefe da lista H
. O segundo elemento é bastante mais difícil de acessar e pode não existir. Felizmente, x
é menor do que todas as tuplas que estamos considerando (por meio do operador de comparação generalizada do Prolog @>=
), para que possamos considerar o "segundo elemento" de uma lista de singleton x
e o programa funcionará bem. Quanto ao acesso real ao segundo elemento, o método tersest é adicionar um terceiro argumento (um argumento out) a b
, que retorna x
no caso base e H
no caso recursivo; isso significa que podemos pegar a cabeça da cauda como uma saída da segunda chamada recursiva para B
e, é claro, a cabeça da cauda é o segundo elemento da lista. Então, b
fica assim agora:
b(x,0,x).
b(T/H,N,H):-N#=A+B+1,b(H,A,_),b(T,B,J),H@>=J.
O caso base é bastante simples (lista vazia, retorna uma contagem de 0, o "primeiro elemento" da lista vazia é x
). O caso recursivo começa da mesma forma como antes (apenas com a T/H
notação em vez de [H|T]
, e H
como um extra para fora argumento); desconsideramos o argumento extra da chamada recursiva na cabeça, mas a armazenamos na J
chamada recursiva na cauda. Então, tudo o que precisamos fazer é garantir que H
seja maior ou igual a J
(ou seja, "se a lista tiver pelo menos dois elementos, o primeiro for maior ou igual ao segundo) para garantir que a lista termine.
Infelizmente, setof
isso se encaixa se tentarmos usar a definição anterior de c
juntamente com essa nova definição de b
, porque ele trata parâmetros não utilizados de maneira mais ou menos da mesma maneira que um SQL GROUP BY
, que não é exatamente o que queremos. É possível reconfigurá-lo para fazer o que queremos, mas essa reconfiguração custa caracteres. Em vez disso, usamos findall
, que possui um comportamento padrão mais conveniente e tem apenas dois caracteres a mais, dando-nos esta definição de c
:
c(X,Y):-findall(A,b(A,X,_),L),length(L,Y).
E esse é o programa completo; gere padrões de bolhas de maneira concisa e depois gaste toda uma carga de bytes contando-os (precisamos de um tempo bastante longo findall
para converter o gerador em uma lista, depois de um nome infelizmente verbal length
para verificar o comprimento dessa lista, além do padrão para uma declaração de função).