*(>:^]*(*>{<-!<:^>[:((-<)<(<!-)>>-_)_<<]>:]<]]}*<)]*(:)*=<*)>]
Precisa ser executado com os -lnsinalizadores da linha de comando (daí +4 bytes). Imprime 0para números compostos e 1para 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 -nsinalizador, a entrada é lida como um número inteiro decimal.
- No final do programa, a pilha atual é usada para saída. Se houver um
-1na 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 -lbandeira: 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 4exemplo, 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 0em 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 -1que 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 1para 0e tudo mais com valores positivos *, então veja como fazemos isso:
*(*...)
Ou seja, nos transformamos 1em 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 ( 0no 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 é 0e o loop para. Vamos tentar os divisores começando em n-1e depois diminuindo-os para 1. Isso significa que a) sabemos que isso terminará quando chegarmos1o 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 -1e diminua). Eles podem ser combinados em um incremento !-ou decremento -!. Então, diminuímos a cópia nem cima da -1, em seguida, fazemos outra cópia nna 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 3será substituído pelo próximo divisor de teste e assim por diante (enquanto as duas cópias de nsempre 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 -ne adicionar repetidamente o divisor de teste daté obtermos um valor positivo. Quando o fazemos, subtraímos o resultado de isso nos dá o restante. O mais complicado aqui é que não podemos simplesmente colocar um -nno 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 ntopo 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 na 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 duma vez ao -nagora):
-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 ae o valor abaixo bdele for substituído apor b-a. Desde que negamos a, porém, -_substitui apor 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 dpara 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 dcom :e empurrá-lo de volta para o -1com ]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émnO maior divisor (exceto n).
Depois que o loop termina, há pouco *<antes de juntarmos os caminhos à entrada 1novamente. 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 = 1em 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 = 3um número primo:
3
1 1 1
... 1 3 -1 1 ...
0 0 0 0
^
Portanto, para números primos, temos um 1nesta pilha e, para números compostos, temos um 0ou um número positivo maior que 2. Transformamos essa situação no 0ou 1precisamos 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 0e 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 0composto e1para 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 -1na direita pilha de que . É para isso que =<*serve. =troca as partes superiores das duas pilhas adjacentes, movendo assim -1para a direita do resultado, por exemplo, para entrada 4novamente:
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 -dsinalizador e insira "onde quiser ver o estado atual da memória, por exemplo , assim , ou use o -Dsinalizador 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.