Stack Cats , 62 + 4 = 66 octets
*(>:^]*(*>{<-!<:^>[:((-<)<(<!-)>>-_)_<<]>:]<]]}*<)]*(:)*=<*)>]
Doit être exécuté avec les -ln
indicateurs de ligne de commande (donc +4 octets). Impressions0
pour les nombres composés et 1
pour les nombres premiers.
Essayez-le en ligne!
Je pense que c'est le premier programme non-trivial de Stack Cats.
Explication
Une rapide introduction à Stack Cats:
- Stack Cats fonctionne sur une bande infinie de piles, avec une tête de bande pointant vers une pile en cours. Chaque pile est initialement remplie d'une quantité infinie de zéros. Je vais généralement ignorer ces zéros dans mon libellé. Par conséquent, lorsque je dis "le bas de la pile", je veux dire la valeur la plus basse non nulle et si je dis "la pile est vide", je veux dire qu'il n'y a que des zéros.
- Avant que le programme ne commence, a
-1
est placé sur la pile initiale, puis toute l'entrée est poussée dessus. Dans ce cas, en raison de l' -n
indicateur, l'entrée est lue sous forme d'entier décimal.
- À la fin du programme, la pile actuelle est utilisée pour la sortie. S'il y a un
-1
en bas, il sera ignoré. De nouveau, en raison de l' -n
indicateur, les valeurs de la pile sont simplement imprimées sous forme d'entiers décimaux séparés par des sauts de ligne.
- Stack Cats est un langage de programme réversible: chaque morceau de code peut être annulé (sans que Stack Cats conserve la trace d’un historique explicite). Plus spécifiquement, pour inverser un morceau de code, il vous suffit de le refléter, par exemple,
<<(\-_)
devient (_-/)>>
. Cet objectif de conception impose des restrictions assez sévères sur les types d'opérateurs et de constructions de flux de contrôle existant dans le langage, ainsi que sur les types de fonctions que vous pouvez calculer sur l'état de la mémoire globale.
Pour couronner le tout, chaque programme Stack Cats doit être symétrique. Vous remarquerez peut-être que ce n'est pas le cas pour le code source ci-dessus. C’est à cela que sert le -l
drapeau: il reflète implicitement le code à gauche, en utilisant le premier caractère du centre. Le programme actuel est donc le suivant:
[<(*>=*(:)*[(>*{[[>[:<[>>_(_-<<(-!>)>(>-)):]<^:>!->}<*)*[^:<)*(>:^]*(*>{<-!<:^>[:((-<)<(<!-)>>-_)_<<]>:]<]]}*<)]*(:)*=<*)>]
Programmer efficacement avec l'ensemble du code est hautement non trivial et peu intuitif et n'a pas encore vraiment compris comment un humain peut le faire. Nous avons forcé ce programme pour des tâches plus simples, mais nous n'aurions pas pu nous en approcher à la main. Heureusement, nous avons trouvé un modèle de base qui vous permet d’ignorer la moitié du programme. Bien que cela soit certainement sous-optimal, c'est actuellement le seul moyen connu de programmer efficacement dans Stack Cats.
Donc, dans cette réponse, le modèle de ce motif est le suivant (la façon dont il est exécuté varie):
[<(...)*(...)>]
Lorsque le programme démarre, la bande de pile ressemble à ceci (pour une entrée 4
, par exemple):
4
... -1 ...
0
^
Le [
déplace le haut de la pile vers la gauche (et la tête de la bande le long) - nous appelons cela "pousser". Et les <
mouvements de la tête de la bande seule. Donc, après les deux premières commandes, nous avons cette situation:
... 4 -1 ...
0 0 0
^
Maintenant, (...)
c’est une boucle qui peut être utilisée assez facilement comme condition: la boucle n’est entrée et laissée que lorsque le sommet de la pile actuelle est positif. Depuis, il est actuellement nul, nous sautons toute la première moitié du programme. Maintenant, la commande centrale est *
. C’est simplement XOR 1
, c’est-à-dire qu’elle bascule le bit le moins significatif du haut de la pile et, dans ce cas, transforme le 0
en 1
:
... 1 4 -1 ...
0 0 0
^
Maintenant, nous rencontrons l'image miroir du (...)
. Cette fois , le sommet de la pile est positive et nous faire entrer dans le code. Avant de regarder ce qui se passe entre les parenthèses, laissez-moi vous expliquer comment nous allons terminer à la fin: nous voulons nous assurer que, à la fin de ce bloc, nous avons à nouveau la tête de la bande sur une valeur positive boucle se termine après une seule itération et est simplement utilisée comme condition linéaire), que la pile à droite contient la sortie et que la pile à droite de celle-ci contient un -1
. Si c'est le cas, nous quittons la boucle, passons >
à la valeur de sortie et la ]
plaçons sur la -1
pile afin d'obtenir une pile vierge pour la sortie.
C'est ça. Maintenant, entre parenthèses, nous pouvons faire ce que nous voulons pour vérifier la primalité tant que nous nous assurons que nous organisons les choses comme décrit dans le paragraphe précédent à la fin (ce qui peut facilement être fait en poussant et en déplaçant la tête de la bande). J'ai d'abord essayé de résoudre le problème avec le théorème de Wilson, mais j'ai finalement dépassé les 100 octets, car le calcul factoriel au carré est en fait assez coûteux dans Stack Cats (du moins, je n'ai pas trouvé le moindre moyen). J'ai donc choisi la division d'essai et cela s'est avéré beaucoup plus simple. Regardons le premier bit linéaire:
>:^]
Vous avez déjà vu deux de ces commandes. En outre, :
échange les deux valeurs supérieures de la pile actuelle et ^
XOR la deuxième valeur en valeur supérieure. Cela crée :^
un modèle courant pour dupliquer une valeur sur une pile vide (nous tirons un zéro au-dessus de la valeur puis transformons le zéro en valeur 0 XOR x = x
). Ainsi, après cela, notre bande ressemble à ceci:
4
... 1 4 -1 ...
0 0 0
^
L'algorithme de division d'essai que j'ai implémenté ne fonctionne pas en entrée 1
, nous devrions donc ignorer le code dans ce cas. Nous pouvons facilement mapper 1
vers 0
et tout le reste avec des valeurs positives *
, alors voici comment nous procédons:
*(*...)
C’est-à-dire que nous nous tournons 1
vers 0
, sautons une grande partie du code si nous obtenons effectivement 0
, mais à l’intérieur, nous annulons immédiatement le code *
afin que nous puissions récupérer notre valeur d’entrée. Nous devons simplement nous assurer à nouveau que nous aboutissons à une valeur positive à la fin des parenthèses afin qu'elles ne commencent pas en boucle. À l'intérieur du conditionnel, nous déplaçons une pile à droite avec le >
, puis nous démarrons la boucle de division de test principale:
{<-!<:^>[:((-<)<(<!-)>>-_)_<<]>:]<]]}
Les accolades (par opposition aux parenthèses) définissent un type de boucle différent: il s'agit d'une boucle do-while, ce qui signifie qu'elle s'exécute toujours pour au moins une itération. L'autre différence est la condition de fin: lors de l'entrée dans la boucle, Stack Cat se souvient de la valeur maximale de la pile actuelle ( 0
dans notre cas). La boucle sera ensuite exécutée jusqu'à ce que cette même valeur soit revue à la fin d'une itération. C'est pratique pour nous: à chaque itération, nous calculons simplement le reste du prochain diviseur potentiel et le déplaçons sur cette pile sur laquelle nous démarrons la boucle. Lorsque nous trouvons un diviseur, le reste l'est 0
et la boucle s'arrête. Nous allons essayer les diviseurs à partir de n-1
, puis les décrémenter 1
. Cela signifie a) nous savons que cela se terminera lorsque nous atteindrons1
plus tard et b) nous pouvons ensuite déterminer si le nombre est premier ou non en inspectant le dernier diviseur que nous avons essayé (si c’est1
, c’est un excellent, sinon ce n’est pas le cas).
Allons-y. Il y a une courte section linéaire au début:
<-!<:^>[:
Vous savez ce que la plupart de ces choses font maintenant. Les nouvelles commandes sont -
et !
. Stack Cats n'a pas d'opérateur d'incrémentation ou de décrémentation. Cependant, il a -
(négation, c'est-à-dire multiplier par -1
) et !
(PAS au niveau du bit, c'est-à-dire multiplier par -1
et décrémenter). Ceux-ci peuvent être combinés en incrément !-
, ou décrément -!
. Donc, nous décrémentons la copie de n
dessus -1
, puis faisons une autre copie de n
la pile à gauche, puis récupérons le nouveau diviseur d’essai et le plaçons en dessous n
. Donc, à la première itération, nous obtenons ceci:
4
3
... 1 4 -1 ...
0 0 0
^
Lors des itérations suivantes, la 3
volonté sera remplacée par le prochain diviseur de test, etc. (alors que les deux copies de n
seront toujours la même valeur à ce stade).
((-<)<(<!-)>>-_)
C'est le calcul modulo. Puisque les boucles se terminent sur des valeurs positives, l’idée est de commencer -n
et d’y ajouter de manière répétée le diviseur d’essai d
jusqu’à obtenir une valeur positive. Une fois que nous faisons, nous soustrayons le résultat de d
et cela nous donne le reste. Le problème ici est que nous ne pouvons pas simplement placer un -n
sommet de la pile et lancer une boucle qui ajoute d
: si le haut de la pile est négatif, la boucle ne sera pas entrée. Telles sont les limites d'un langage de programmation réversible.
Donc, pour contourner ce problème, nous commençons par n
en haut de la pile, mais nous le nions seulement à la première itération. Encore une fois, cela semble plus simple que cela s'avère être ...
(-<)
Lorsque le sommet de la pile est positif (c'est-à-dire uniquement à la première itération), nous le nions avec -
. Cependant, nous ne pouvons pas le faire (-)
parce que nous ne serions pas quittions la boucle jusqu'à ce que -
a été appliquée deux fois. Donc, nous déplaçons une cellule avec <
parce que nous savons qu’il ya une valeur positive (la 1
). Ok, alors maintenant nous avons nié de manière fiable n
la première itération. Mais nous avons un nouveau problème: la tête de la bande est maintenant dans une position différente lors de la première itération. Nous devons consolider cela avant de continuer. La prochaine <
déplace la tête de la bande à gauche. La situation à la première itération:
-4
3
... 1 4 -1 ...
0 0 0 0
^
Et à la deuxième itération (rappelez-vous que nous avons ajouté d
une fois -n
maintenant):
-1
3
... 1 4 -1 ...
0 0 0
^
Le conditionnel suivant fusionne à nouveau ces chemins:
(<!-)
Lors de la première itération, la tête de la bande pointe à zéro et est donc entièrement ignorée. Lors de nouvelles itérations, la tête de la bande pointe sur une unité, nous l'exécutons donc, nous nous déplaçons à gauche et incrémentons la cellule à cet endroit. Comme nous savons que la cellule commence à zéro, elle sera désormais toujours positive pour pouvoir quitter la boucle. Cela garantit que nous finissons toujours par deux piles à gauche de la pile principale et que nous pouvons maintenant revenir en arrière >>
. Ensuite, à la fin de la boucle modulo, nous le faisons -_
. Tu sais déjà -
. _
est de soustraction ce qui ^
est de XOR: si le haut de la pile est a
et la valeur est au- dessous b
il remplace a
avec b-a
. Depuis que nous avons tout d'abord nié a
, -_
remplace a
par dans notre total cumulé.b+a
, ajoutant ainsid
Une fois la boucle terminée (nous avons atteint une valeur positive), la bande ressemble à ceci:
2
3
... 1 1 4 -1 ...
0 0 0 0
^
La valeur la plus à gauche peut être n'importe quel nombre positif. En fait, c'est le nombre d'itérations moins un. Il y a un autre bit linéaire court maintenant:
_<<]>:]<]]
Comme je l'ai dit plus tôt, nous devons soustraire le résultat d
pour obtenir le reste réel ( 3-2 = 1 = 4 % 3
), nous le faisons donc _
une fois de plus. Ensuite, nous devons nettoyer la pile que nous avons incrémentée à gauche: lorsque nous essayons le prochain diviseur, il doit être à nouveau égal à zéro pour que la première itération fonctionne. Nous nous déplaçons donc là-bas et plaçons cette valeur positive sur l'autre pile d'assistance <<]
, puis revenons sur notre pile opérationnelle avec une autre >
. Nous nous arrêtons d
avec :
et le repoussons sur -1
avec ]
puis nous plaçons le reste sur notre pile conditionnelle avec <]]
. C’est la fin de la boucle de division d’essai: elle se poursuit jusqu’à ce que nous obtenions un reste nul, auquel cas la pile à gauche contientn
Le plus grand diviseur (autre que n
).
Une fois la boucle terminée, il nous reste juste *<
avant de rejoindre à nouveau les chemins avec l’entrée 1
. Il *
transforme simplement le zéro en un 1
, ce dont nous avons besoin dans un instant, puis nous passons au diviseur avec <
(pour que nous soyons sur la même pile que pour l’entrée 1
).
À ce stade, il est utile de comparer trois types d’entrées différents. Premièrement, le cas spécial n = 1
où nous n’avons rien fait de ce genre de division:
0
... 1 1 -1 ...
0 0 0
^
Ensuite, notre exemple précédent n = 4
, un nombre composé:
2
1 2 1
... 1 4 -1 1 ...
0 0 0 0
^
Et enfin, n = 3
un nombre premier:
3
1 1 1
... 1 3 -1 1 ...
0 0 0 0
^
Donc, pour les nombres premiers, nous avons un 1
sur cette pile, et pour les nombres composés, nous avons un 0
nombre positif ou supérieur à 2
. Nous transformons cette situation en 0
ou 1
nous avons besoin avec le code final suivant:
]*(:)*=<*
]
pousse simplement cette valeur vers la droite. Ensuite , *
est utilisé pour simplifier la situation conditionnelle fortement: en activant le moins important, nous nous tournons 1
(premier) en 0
, 0
(composite) dans la valeur positive 1
, et toutes les autres valeurs positives restent toujours positives. Il ne reste plus qu’à faire la distinction entre 0
positif et positif. C'est là que nous en utilisons un autre (:)
. Si le haut de la pile est 0
(et que l'entrée était un nombre premier), ceci est simplement ignoré. Mais si le sommet de la pile est positif (et l’entrée était un nombre composite), elle est échangée avec le 1
, de sorte que nous avons maintenant 0
pour composite et1
pour les nombres premiers - seulement deux valeurs distinctes. Bien sûr, ils sont le contraire de ce que nous voulons produire, mais cela se corrige facilement avec un autre *
.
Maintenant , tout ce qui reste est de restaurer le modèle des piles attendues par notre cadre environnant: la tête de bande sur une valeur positive, résultat au - dessus de la pile à droite, et un seul -1
sur la droite de la pile de ce . C'est à quoi ça =<*
sert. =
permute les sommets des deux piles adjacentes, ce qui déplace le -1
vers la droite du résultat, par exemple, pour une 4
nouvelle saisie :
2 0
1 3
... 1 4 1 -1 ...
0 0 0 0 0
^
Ensuite, nous passons à gauche avec <
et transformons ce zéro en un avec *
. Et c'est ça.
Si vous souhaitez approfondir le fonctionnement du programme, vous pouvez utiliser les options de débogage. Ajoutez l’ -d
indicateur et insérez-le "
où vous voulez voir l’état actuel de la mémoire, par exemple , ou utilisez l’ -D
indicateur pour obtenir une trace complète du programme entier . Sinon, vous pouvez utiliser EsotericIDE de Timwi, qui inclut un interpréteur Stack Cats avec un débogueur pas à pas.