*(>:^]*(*>{<-!<:^>[:((-<)<(<!-)>>-_)_<<]>:]<]]}*<)]*(:)*=<*)>]
Precisa ser executado com os -ln
sinalizadores da linha de comando (daí +4 bytes). Imprime 0
para números compostos e 1
para números primos.
Experimente online!
Acho que este é o primeiro programa não-trivial do Stack Cats.
Explicação
Uma rápida introdução ao Stack Cats:
- O Stack Cats opera em uma fita infinita de pilhas, com uma cabeça de fita apontando para uma pilha atual. Cada pilha é inicialmente preenchida com uma quantidade infinita de zeros. Geralmente ignorarei esses zeros na minha redação, portanto, quando digo "a parte inferior da pilha", quero dizer o menor valor diferente de zero e se digo "a pilha está vazia", quero dizer que há apenas zeros nela.
- Antes do programa iniciar, a
-1
é empurrada para a pilha inicial e, em seguida, toda a entrada é empurrada para cima dela. Nesse caso, devido ao -n
sinalizador, a entrada é lida como um número inteiro decimal.
- No final do programa, a pilha atual é usada para saída. Se houver um
-1
na parte inferior, ele será ignorado. Mais uma vez, devido à-n
sinalizador, os valores da pilha são simplesmente impressos como números inteiros decimais separados por avanço de linha.
- O Stack Cats é uma linguagem de programa reversível: todo código pode ser desfeito (sem o Stack Cats acompanhar um histórico explícito). Mais especificamente, para reverter qualquer parte do código, você simplesmente o espelha, por exemplo,
<<(\-_)
torna-se(_-/)>>
. Esse objetivo de design impõe restrições bastante severas a quais tipos de operadores e construções de fluxo de controle existem no idioma e que tipos de funções você pode calcular no estado da memória global.
Para completar, todo programa Stack Cats precisa ser auto-simétrico. Você pode perceber que esse não é o caso do código fonte acima. É para isso que serve a -l
bandeira: espelha implicitamente o código à esquerda, usando o primeiro caractere para o centro. Portanto, o programa real é:
[<(*>=*(:)*[(>*{[[>[:<[>>_(_-<<(-!>)>(>-)):]<^:>!->}<*)*[^:<)*(>:^]*(*>{<-!<:^>[:((-<)<(<!-)>>-_)_<<]>:]<]]}*<)]*(:)*=<*)>]
Programar efetivamente com todo o código é altamente não trivial e não intuitivo e ainda não descobriu como um humano pode fazê-lo. Forçamos esse programa brutalmente para tarefas mais simples, mas não conseguimos chegar nem perto disso à mão. Felizmente, encontramos um padrão básico que permite ignorar metade do programa. Embora isso seja certamente subótimo, atualmente é a única maneira conhecida de programar efetivamente no Stack Cats.
Portanto, nesta resposta, o modelo do referido padrão é este (há alguma variabilidade na forma como é executado):
[<(...)*(...)>]
Quando o programa é iniciado, a fita da pilha fica assim (por 4
exemplo, para entrada ):
4
... -1 ...
0
^
O [
move o topo da pilha para a esquerda (e a cabeça da fita) - chamamos isso de "empurrar". E isso <
move a cabeça da fita sozinha. Então, após os dois primeiros comandos, temos esta situação:
... 4 -1 ...
0 0 0
^
Agora o (...)
é um loop que pode ser usado facilmente como condicional: o loop é inserido e deixado apenas quando a parte superior da pilha atual é positiva. Como atualmente é zero, pulamos a primeira metade inteira do programa. Agora o comando central é *
. Isso é simplesmente XOR 1
, ou seja, alterna o bit menos significativo do topo da pilha e, nesse caso, transforma o 0
em um 1
:
... 1 4 -1 ...
0 0 0
^
Agora encontramos a imagem invertida do (...)
. Desta vez, o topo da pilha é positivo e vamos fazer introduzir o código. Antes de analisarmos o que acontece entre parênteses, deixe-me explicar como encerraremos no final: queremos garantir que, no final deste bloco, tenhamos a cabeça da fita com um valor positivo novamente (para que o loop termina após uma única iteração e é usado simplesmente como uma condicional linear), que a pilha para a direita contém a saída e que o direito de pilha que detém um -1
. Se for esse o caso, deixamos o loop, passamos >
para o valor de saída e o ]
pressionamos para -1
que possamos ter uma pilha limpa de saída.
É isso. Agora, dentro dos parênteses, podemos fazer o que quisermos para verificar a primalidade, desde que asseguremos que configuramos as coisas conforme descrito no parágrafo anterior no final (o que pode ser feito facilmente com alguns movimentos de empurrar e mover a cabeça da fita). Tentei primeiro resolver o problema com o teorema de Wilson, mas acabei com mais de 100 bytes, porque a computação fatorial ao quadrado é realmente muito cara no Stack Cats (pelo menos não encontrei um caminho curto). Então, eu fui com a divisão de testes e isso realmente ficou muito mais simples. Vejamos o primeiro bit linear:
>:^]
Você já viu dois desses comandos. Além disso, alterna os :
dois principais valores da pilha atual e ^
XORs o segundo valor no valor superior. Isso cria :^
um padrão comum para duplicar um valor em uma pilha vazia (extraímos um zero por cima do valor e depois o transformamos em zero 0 XOR x = x
). Então, depois disso, nossa seção de fita fica assim:
4
... 1 4 -1 ...
0 0 0
^
O algoritmo de divisão de teste que eu implementei não funciona para entrada 1
, portanto, devemos pular o código nesse caso. Podemos mapear facilmente 1
para 0
e tudo mais com valores positivos *
, então veja como fazemos isso:
*(*...)
Ou seja, nos transformamos 1
em 0
, pular uma grande parte do código, se conseguirmos de fato 0
, mas por dentro nós imediatamente desfazemos o código *
para que recuperemos nosso valor de entrada. Só precisamos garantir novamente que terminemos com um valor positivo no final dos parênteses para que eles não comecem a repetir. Dentro do condicional, movemos uma pilha à direita com o >
e, em seguida, iniciamos o loop principal da divisão de teste:
{<-!<:^>[:((-<)<(<!-)>>-_)_<<]>:]<]]}
As chaves (em oposição aos parênteses) definem um tipo diferente de loop: é um loop do-while, o que significa que sempre é executado por pelo menos uma iteração. A outra diferença é a condição de terminação: ao entrar no loop, o Stack Cat lembra o valor superior da pilha atual ( 0
no nosso caso). O loop será executado até que esse mesmo valor seja visto novamente no final de uma iteração. Isso é conveniente para nós: em cada iteração, simplesmente calculamos o restante do próximo divisor em potencial e o movemos para essa pilha em que estamos iniciando o loop. Quando encontramos um divisor, o restante é 0
e o loop para. Vamos tentar os divisores começando em n-1
e depois diminuindo-os para 1
. Isso significa que a) sabemos que isso terminará quando chegarmos1
o mais tardar eb) podemos determinar se o número é primo ou não, inspecionando o último divisor que tentamos (se for 1
, é primo, caso contrário, não é).
Vamos lá. Há uma seção linear curta no começo:
<-!<:^>[:
Você sabe o que a maioria dessas coisas faz até agora. Os novos comandos são -
e !
. O Stack Cats não possui operadores de incremento ou decremento. No entanto, possui -
(negação, isto é, multiplique por -1
) e !
(bit a bit NÃO, ou seja, multiplique por -1
e diminua). Eles podem ser combinados em um incremento !-
ou decremento -!
. Então, diminuímos a cópia n
em cima da -1
, em seguida, fazemos outra cópia n
na pilha à esquerda, buscamos o novo divisor de avaliação e colocamos em baixo n
. Então, na primeira iteração, obtemos o seguinte:
4
3
... 1 4 -1 ...
0 0 0
^
Em iterações adicionais, o 3
será substituído pelo próximo divisor de teste e assim por diante (enquanto as duas cópias de n
sempre terão o mesmo valor neste momento).
((-<)<(<!-)>>-_)
Este é o cálculo do módulo. Como os loops terminam com valores positivos, a idéia é começar -n
e adicionar repetidamente o divisor de teste d
até obtermos um valor positivo. Quando o fazemos, subtraímos o resultado d
e isso nos dá o restante. O mais complicado aqui é que não podemos simplesmente colocar um -n
no topo da pilha e iniciar um loop que acrescenta d
: se o topo da pilha for negativo, o loop não será inserido. Essas são as limitações de uma linguagem de programação reversível.
Portanto, para contornar esse problema, começamos com o n
topo da pilha, mas o negamos apenas na primeira iteração. Novamente, isso parece mais simples do que parece ser ...
(-<)
Quando o topo da pilha é positivo (ou seja, apenas na primeira iteração), nós o negamos -
. No entanto, não podemos fazer isso (-)
porque não sairíamos do loop até que -
fosse aplicado duas vezes. Então, movemos uma célula para a esquerda <
porque sabemos que há um valor positivo lá (o 1
). Ok, agora negamos de maneira confiável n
a primeira iteração. Mas temos um novo problema: a cabeça da fita agora está em uma posição diferente na primeira iteração do que em todas as outras. Precisamos consolidar isso antes de seguirmos em frente. O próximo <
move a cabeça da fita para a esquerda. A situação na primeira iteração:
-4
3
... 1 4 -1 ...
0 0 0 0
^
E na segunda iteração (lembre-se de que adicionamos d
uma vez ao -n
agora):
-1
3
... 1 4 -1 ...
0 0 0
^
O próximo condicional mescla esses caminhos novamente:
(<!-)
Na primeira iteração, a cabeça da fita aponta para zero, portanto isso é ignorado completamente. Em outras iterações, a cabeça da fita aponta para uma, porém, executamos isso, movemos para a esquerda e incrementamos a célula lá. Como sabemos que a célula começa do zero, agora sempre será positiva para que possamos sair do loop. Isso garante que sempre terminemos com duas pilhas restantes da pilha principal e agora podemos voltar com ela >>
. Então, no final do ciclo do módulo, fazemos -_
. Você já sabe -
. _
é subtrair o que ^
é XOR: se o topo da pilha estiver a
e o valor abaixo b
dele for substituído a
por b-a
. Desde que negamos a
, porém, -_
substitui a
por b+a
, adicionandod
em nosso total de corrida.
Depois que o loop termina (atingimos um valor positivo), a fita fica assim:
2
3
... 1 1 4 -1 ...
0 0 0 0
^
O valor mais à esquerda pode ser qualquer número positivo. De fato, é o número de iterações menos uma. Há outro pequeno bit linear agora:
_<<]>:]<]]
Como eu disse anteriormente, precisamos subtrair o resultado de d
para obter o restante real ( 3-2 = 1 = 4 % 3
), então fazemos apenas _
mais uma vez. Em seguida, precisamos limpar a pilha que temos incrementado à esquerda: quando tentamos o próximo divisor, ele precisa ser zero novamente, para que a primeira iteração funcione. Então, vamos para lá e colocamos esse valor positivo na outra pilha auxiliar <<]
e depois voltamos à nossa pilha operacional com outra >
. Nós puxar para cima d
com :
e empurrá-lo de volta para o -1
com ]
e depois passamos o restante em nossa pilha condicional com <]]
. Esse é o fim do loop de divisão de teste: isso continua até obtermos um zero restante, nesse caso a pilha à esquerda contémn
O maior divisor (exceto n
).
Depois que o loop termina, há pouco *<
antes de juntarmos os caminhos à entrada 1
novamente. O *
simplesmente transforma o zero em um 1
, o que precisaremos daqui a pouco, e depois passaremos para o divisor <
(para que estejamos na mesma pilha da entrada 1
).
Neste ponto, ajuda a comparar três tipos diferentes de entradas. Primeiro, o caso especial n = 1
em que não fizemos nada disso:
0
... 1 1 -1 ...
0 0 0
^
Então, nosso exemplo anterior n = 4
, um número composto:
2
1 2 1
... 1 4 -1 1 ...
0 0 0 0
^
E, finalmente, n = 3
um número primo:
3
1 1 1
... 1 3 -1 1 ...
0 0 0 0
^
Portanto, para números primos, temos um 1
nesta pilha e, para números compostos, temos um 0
ou um número positivo maior que 2
. Transformamos essa situação no 0
ou 1
precisamos com o seguinte trecho de código final:
]*(:)*=<*
]
apenas empurra esse valor para a direita. Então *
é usado para simplificar bastante a situação condicional: alternando o bit menos significativo, transformamos 1
(primo) em 0
, 0
(composto) no valor positivo 1
, e todos os outros valores positivos ainda permanecerão positivos. Agora só precisamos distinguir entre 0
e positivo. É aí que usamos outro (:)
. Se o topo da pilha for 0
(e a entrada for primordial), isso será simplesmente ignorado. Mas se o topo da pilha for positivo (e a entrada for um número composto), isso será trocado com o 1
, de modo que agora temos o 0
composto e1
para números primos - apenas dois valores distintos. Claro, eles são o oposto do que queremos produzir, mas isso é facilmente corrigido com outro *
.
Agora tudo o que resta é para restaurar o padrão de pilhas esperados por nosso quadro envolvente: Cabeça de fita em um valor positivo, resultar no topo da pilha para a direita, e um único -1
na direita pilha de que . É para isso que =<*
serve. =
troca as partes superiores das duas pilhas adjacentes, movendo assim -1
para a direita do resultado, por exemplo, para entrada 4
novamente:
2 0
1 3
... 1 4 1 -1 ...
0 0 0 0 0
^
Então, apenas movemos para a esquerda <
e transformamos esse zero em um com *
. E é isso.
Se você quiser se aprofundar no funcionamento do programa, use as opções de depuração. Adicione o -d
sinalizador e insira "
onde quiser ver o estado atual da memória, por exemplo , assim , ou use o -D
sinalizador para obter um rastreamento completo de todo o programa . Como alternativa, você pode usar o EsotericIDE de Timwi, que inclui um interpretador Stack Cats com um depurador passo a passo.