Fission , 1328 989 887 797 octets
Cette réponse est un peu déraisonnablement longue (j'aimerais que nous ayons des régions réductibles ) ... s'il vous plaît, n'oubliez pas de faire défiler la page au-delà et de montrer aux autres réponses un peu d'amour!
Travailler sur ce code est ce qui a inspiré ce défi. Je voulais ajouter une réponse dans Fission à EOEIS, ce qui m'a amené à cette séquence. Cependant, pour apprendre réellement la fission et l’appliquer, il a fallu quelques semaines pour travailler dessus. Entre temps, la séquence avait vraiment pris de l'ampleur et j'ai donc décidé de poster un défi séparé pour elle (en plus, cela n'aurait pas été particulièrement long en bas de l'arbre sur EOEIS).
Je vous présente donc la monstruosité:
R'0@+\
/ Y@</ /[@ Y]_L
[? % \ / \ J
\$@ [Z/;[{+++++++++L
UR+++++++++>/;
9\ ; 7A9
SQS {+L /$ \/\/\/\/\/ 5/ @ [~ &@[S\/ \ D /8/
~4X /A@[ %5 /; & K } [S//~KSA /
3 \ A$@S S\/ \/\/\/ \/>\ /S]@A / \ { +X
W7 X X /> \ +\ A\ / \ /6~@/ \/
/ ~A\; +;\ /@
ZX [K / {/ / @ @ } \ X @
\AS </ \V / }SZS S/
X ;;@\ /;X /> \ ; X X
; \@+ >/ }$S SZS\+; //\V
/ \\ /\; X X @ @ \~K{
\0X / /~/V\V / 0W//
\ Z [K \ //\
W /MJ $$\\ /\7\A /;7/\/ /
4}K~@\ &] @\ 3/\
/ \{ }$A/1 2 }Y~K <\
[{/\ ;@\@ / \@<+@^ 1;}++@S68
@\ <\ 2 ; \ /
$ ;}++ +++++++L
%@A{/
M \@+>/
~ @
SNR'0YK
\ A!/
Il s'attend à ce qu'il n'y ait pas de fin de ligne sur l'entrée, vous pouvez donc l'appeler comme echo -n 120 | ./Fission oeis256504.fis
.
La mise en page pourrait probablement encore être plus efficace, alors je pense qu'il reste encore beaucoup à faire (par exemple, il contient 911 581 461 374 espaces).
Avant de passer à l'explication, une note sur le test: l' interprète officiel ne fonctionne pas entièrement tel quel. a) Mirror.cpp
ne compile pas sur beaucoup de systèmes. Si vous rencontrez ce problème, commentez la ligne incriminée - le composant affecté (un miroir aléatoire) n'est pas utilisé dans ce code. b) Quelques bugs peuvent conduire à un comportement indéfini (et probablement pour un programme aussi complexe). Vous pouvez appliquer ce correctif pour les réparer. Une fois que vous avez fait cela, vous devriez pouvoir compiler l’interprète avec
g++ -g --std=c++11 *.cpp -o Fission
Fait amusant: ce programme utilise presque tous les composants que la fission a à offrir, à l'exception de #
(miroir aléatoire), :
(demi-miroir) -
ou |
(miroir simple) et "
(mode d'impression).
Quoi sur Terre?
Attention: Ce sera assez long ... Je suppose que vous êtes vraiment intéressé par le fonctionnement de Fission et par la façon dont vous pourriez le programmer. Parce que si vous ne l'êtes pas, je ne sais pas comment je pourrais résumer ceci. (Le paragraphe suivant donne cependant une description générale de la langue.)
La fission est un langage de programmation bidimensionnel où les données et le flux de contrôle sont représentés par des atomes se déplaçant à travers une grille. Si vous avez déjà vu ou utilisé Marbelous auparavant, le concept devrait être vaguement familier. Chaque atome a deux propriétés entières: une masse non négative et une énergie arbitraire. Si la masse devient jamais négative, l'atome est retiré de la grille. Dans la plupart des cas, vous pouvez traiter la masse comme la "valeur" de l'atome et l'énergie comme une sorte de méta-propriété utilisée par plusieurs composants pour déterminer le flux des atomes (c'est-à-dire que la plupart des commutateurs dépendent du signe de l'énergie). Je vais désigner des atomes par (m,E)
, si nécessaire. Au début du programme, la grille commence par un tas de(1,0)
des atomes où que vous vous trouviez sur l'un des quatre composants UDLR
(où la lettre indique la direction dans laquelle l'atome se déplace initialement). Le tableau est ensuite peuplé de nombreux composants qui changent la masse et l’énergie des atomes, changent leur direction ou font d’autres choses plus sophistiquées. Pour une liste complète, voir la page esolangs , mais je vais en présenter la plupart dans cette explication. Un autre point important (que le programme utilise plusieurs fois) est que la grille est toroïdale: un atome qui frappe l'un des côtés réapparaît du côté opposé, se déplaçant dans la même direction.
J'ai écrit le programme en plusieurs parties plus petites et les ai assemblées à la fin. C'est ainsi que je vais passer en revue les explications.
atoi
Ce composant peut sembler plutôt inintéressant, mais il est simple et agréable et me permet d’introduire un grand nombre des concepts importants de l’arithmétique et du flux de contrôle de Fission. Par conséquent, je vais passer en revue cette partie avec des détails assez minutieux afin de pouvoir réduire les autres parties à l'introduction de nouveaux mécanismes de fission et au fait de signaler des composants de niveau supérieur dont vous devez pouvoir suivre le flux de contrôle détaillé.
La fission peut uniquement lire les valeurs d'octet de caractères individuels, et non de nombres entiers. Bien que ce soit une pratique acceptable ici, je me suis dit que je pouvais le faire correctement et analyser les entiers réels sur STDIN. Voici le atoi
code:
;
R'0@+\
/ Y@</ /[@ Y]_L
[? % \ / \ J
\$@ [Z/;[{+++++++++L
UR+++++++++>/;
O
Deux des composants les plus importants de la fission sont les réacteurs de fission et de fusion. Les réacteurs à fission sont l’un des types V^<>
(le code ci-dessus utilise <
et >
). Un réacteur à fission peut stocker un atome (en l’envoyant dans le coin du personnage), la valeur par défaut (2,0)
. Si un atome frappe le sommet du personnage, deux nouveaux atomes seront envoyés aux côtés. Leur masse est déterminée en divisant la masse entrante par la masse stockée (c'est-à-dire divisant par deux en moitié) - l'atome de gauche obtient cette valeur et l'atome de droite reçoit le reste de la masse (c'est-à-dire que la masse est conservée dans la fission). . Les deux atomes sortants auront l'énergie entrante moinsl'énergie stockée. Cela signifie que nous pouvons utiliser des réacteurs de fission pour l'arithmétique, aussi bien pour la soustraction que pour la division. Si un réacteur de fission est touché depuis le site, l'atome est simplement réfléchi en diagonale et se dirigera ensuite vers le sommet du personnage.
Les réacteurs de fusion sont l'un des YA{}
(le code ci-dessus utilise Y
et {
). Leur fonction est similaire: ils peuvent stocker un atome (par défaut (1,0)
) et, lorsqu'ils sont touchés au sommet, deux nouveaux atomes sont envoyés aux côtés. Cependant, dans ce cas, les deux atomes seront identiques, conservant toujours l’énergie entrante et multipliant la masse entrante par la masse stockée. Autrement dit, par défaut, le réacteur à fusion duplique simplement tout atome atteignant son sommet. Quand ils touchent des côtés, les réacteurs à fusion sont un peu plus compliqués: l’atome est égalementstocké (indépendamment de l'autre mémoire) jusqu'à ce qu'un atome frappe le côté opposé. Lorsque cela se produit, un nouvel atome est libéré en direction du sommet dont la masse et l’énergie sont la somme des deux atomes anciens. Si un nouvel atome frappe le même côté avant qu'un atome correspondant n'atteigne le côté opposé, l'ancien atome sera simplement écrasé. Les réacteurs de fusion peuvent être utilisés pour mettre en œuvre l'addition et la multiplication.
Une autre composante simple que je veux sortir de la voie est [
et ]
qui a simplement mis la direction de l'atome à droite et à gauche, respectivement (quelle que soit la direction entrante). Les équivalents verticaux sont M
(bas) et W
(haut) mais ils ne sont pas utilisés pour le atoi
code. UDLR
agissent également comme WM][
après avoir libéré leurs atomes initiaux.
Quoi qu'il en soit, regardons le code là-haut. Le programme commence avec 5 atomes:
- Les
R
et L
au bas obtiennent simplement que leur incrément de masse (avec +
) devienne (10,0)
, puis stocké dans un réacteur à fission et dans un réacteur à fusion, respectivement. Nous allons utiliser ces réacteurs pour analyser l’entrée base 10.
- Le
L
dans le coin supérieur droit obtient sa masse décrémentée (à _
) pour devenir (0,0)
et est stocké dans le côté d'un réacteur à fusion Y
. C'est pour garder une trace du nombre que nous lisons - nous allons l'augmenter et le multiplier progressivement au fur et à mesure que nous lisons des chiffres.
- Dans
R
le coin supérieur gauche, la masse est réglée sur le code de caractère de 0
(48) '0
, puis la masse et l’énergie sont échangées contre la dernière @
et enfin augmentées une fois avec +
pour donner (1,48)
. Il est ensuite redirigé avec des miroirs diagonaux \
et /
stocké dans un réacteur à fission. Nous allons utiliser la 48
soustraction pour transformer l'entrée ASCII en valeurs réelles des chiffres. Nous avons également dû augmenter la masse 1
pour éviter la division par 0
.
- Enfin, le
U
coin en bas à gauche est ce qui met tout en mouvement et est utilisé à l’origine uniquement pour le contrôle du flux.
Après avoir été redirigé vers la droite, l'atome de contrôle frappe ?
. C'est le composant d'entrée. Il lit un caractère et définit la masse de l'atome sur la valeur ASCII lue et sur l'énergie 0
. Si nous touchons EOF à la place, l'énergie sera réglée sur 1
.
L'atome continue et ensuite frappe %
. Ceci est un commutateur de miroir. Pour les énergies non positives, cela agit comme un /
miroir. Mais pour l'énergie positive, il agit comme un \
(et diminue également l'énergie de 1). Ainsi, pendant que nous lisons les caractères, l'atome sera réfléchi vers le haut et nous pouvons traiter le personnage. Mais lorsque nous en aurons terminé avec l'entrée, l'atome sera réfléchi vers le bas et nous pouvons appliquer une logique différente pour extraire le résultat. Pour votre information, le composant opposé est &
.
Nous avons donc un atome en mouvement pour le moment. Ce que nous voulons faire pour chaque caractère est de lire sa valeur numérique, de l'ajouter à notre total cumulé, puis de multiplier ce total cumulé par 10 pour préparer le prochain chiffre.
L'atome de caractère frappe d'abord un réacteur de fusion (par défaut) Y
. Cela divise l'atome et nous utilisons la copie de gauche comme atome de contrôle pour revenir au composant d'entrée et lire le caractère suivant. La copie de droite sera traitée. Prenons le cas où nous avons lu le personnage 3
. Notre atome sera (51,0)
. Nous échangeons de la masse et de l’énergie @
, de manière à pouvoir utiliser la soustraction du prochain réacteur à fission. Le réacteur soustrait 48
l'énergie (sans changer la masse), donc il envoie deux copies de (0,3)
- l'énergie correspond maintenant au chiffre que nous avons lu. La copie montante est simplement supprimée avec ;
(un composant qui détruit tous les atomes entrants). Nous continuerons à travailler avec la copie descendante. Vous devrez suivre son chemin à travers le/
et \
reflète un peu.
Le @
réacteur juste avant le réacteur de fusion échange à nouveau de la masse et de l’énergie, de sorte que nous ajouterons (3,0)
à notre total courant dans le Y
. Notez que le total cumulé aura donc toujours de l' 0
énergie.
Maintenant J
c'est un saut. Ce qu'il fait, c'est sauter tout atome entrant par son énergie. Si c'est le cas 0
, l'atome continue à avancer tout droit. Si c'est le 1
cas, une cellule sera ignorée, si c'est le cas, 2
deux cellules, etc. L'énergie est dépensée dans le saut, de sorte que l'atome se termine toujours avec de l'énergie 0
. Puisque le total en cours a une énergie nulle, le saut est ignoré pour le moment et l’atome est redirigé dans le réacteur de fusion {
qui multiplie sa masse par 10
. La copie descendante est rejetée avec ;
la copie montante qui est réinjectée dans le Y
réacteur en tant que nouveau total courant.
Ce qui précède continue à se répéter (d'une manière amusante, où les nouveaux chiffres sont traités avant les précédents) jusqu'à ce que nous atteignions EOF. Maintenant le %
va envoyer l'atome vers le bas. L'idée est de transformer cet atome en (0,1)
maintenant avant de frapper le réacteur à total en marche de manière à ce que a) le total ne soit pas affecté (masse nulle) et b) nous obtenions une énergie de 1
sursaut [
. Nous pouvons facilement prendre soin de l'énergie avec $
, ce qui augmente l'énergie.
Le problème est que ?
cela ne réinitialise pas la masse lorsque vous appuyez sur EOF, la masse sera donc toujours celle du dernier caractère lu et l'énergie sera 0
(parce que %
décrémenté le 1
retour à 0
). Nous voulons donc nous débarrasser de cette masse. Pour ce faire, nous échangeons masse et énergie avec à @
nouveau.
Je dois introduire un élément supplémentaire avant de terminer cette section: Z
. C'est essentiellement la même chose que %
ou &
. La différence est qu’elle laisse passer les atomes d’énergie positive (tout en décrémentant l’énergie) et fait dévier les atomes d’énergie non positive de 90 degrés vers la gauche. Nous pouvons utiliser ceci pour éliminer l'énergie d'un atome en l'enroulant à Z
plusieurs reprises - dès que l'énergie aura disparu, l'atome sera dévié et quittera la boucle. C'est ce motif:
/ \
[Z/
où l'atome se déplacera vers le haut une fois que l'énergie sera nulle. Je vais utiliser ce modèle sous une forme ou une autre plusieurs fois dans les autres parties du programme.
Ainsi , lorsque l'atome quitte cette petite boucle, il sera (1,0)
et swappé (0,1)
par le @
avant de frapper le réacteur de fusion pour libérer le résultat final de l'entrée. Cependant, le total cumulé sera multiplié par 10, car nous l'avons déjà provisoirement multiplié pour un autre chiffre.
Alors maintenant, avec l’énergie 1
, cet atome sautera le [
et sautera dans le /
. Cela le dévie dans un réacteur à fission que nous nous sommes préparés à diviser par 10 et à réparer notre multiplication superflue. Encore une fois, nous jetons une moitié avec ;
et conservons l’autre en sortie (ici, O
nous affichons ici le caractère correspondant et la destruction de l’atome - dans le programme complet, nous continuons à utiliser l’atome à la place).
itoa
/ \
input -> [{/\ ;@
@\ <\
$ ;}++ +++++++L
%@A{/
M \@+>/
~ @
SNR'0YK
\ A!/
Bien entendu, nous devons également reconvertir le résultat en chaîne et l’imprimer. C'est ce que cette partie est pour. Cela suppose que l’entrée n’arrive pas avant la case 10, mais dans le programme complet qui est facilement donné. Ce bit peut être trouvé au bas du programme complet.
Ce code introduit un nouveau composant très puissant dans la fission: la pile K
. La pile est initialement vide. Lorsqu'un atome d'énergie non négative frappe la pile, l'atome est simplement poussé sur la pile. Lorsqu'un atome d'énergie négative frappe la pile, sa masse et son énergie sont remplacées par l'atome situé en haut de la pile (qui est ainsi éclaté). Si la pile est vide, le sens de l'atome est inversé et son énergie devient positive (c'est-à-dire multipliée par -1
).
Ok, revenons au code actuel. L'idée de l' itoa
extrait de code est de prendre à plusieurs reprises l'entrée modulo 10 pour trouver le prochain chiffre tout en divisant l'entier par 10 pour la prochaine itération. Cela donnera tous les chiffres dans l'ordre inverse (du moins significatif au plus significatif). Pour fixer cet ordre, nous plaçons tous les chiffres sur une pile et, à la fin, les supprimons un par un pour les imprimer.
La moitié supérieure du code fait le calcul du chiffre: le L
avec les plus donne un 10 que nous clonons et introduisons dans un réacteur à fission et un réacteur à fusion afin que nous puissions diviser et multiplier par 10. La boucle commence essentiellement après le [
dans le coin supérieur gauche . La valeur actuelle est divisée: une copie est divisée par 10, puis multipliée par 10 et stockée dans un réacteur de fission, qui est ensuite frappé par l'autre copie au sommet. Cela calcule i % 10
comme i - ((i/10) * 10)
. Notez également que la A
division du résultat intermédiaire après la division et avant la multiplication, de sorte que nous pouvons alimenter i / 10
à la prochaine itération.
Il %
abandonne la boucle une fois que la variable d'itération atteint 0. Puisqu'il s'agit plus ou moins d'une boucle do-while, ce code pourrait même fonctionner pour l'impression 0
(sans créer de zéros non plus, sinon). Une fois que nous avons quitté la boucle, nous voulons vider la pile et imprimer les chiffres. S
est le contraire de Z
, c’est donc un commutateur qui va dévier un atome entrant avec une énergie non positive de 90 degrés vers la droite. Ainsi, l'atome se déplace réellement sur le bord du S
droit au premier K
pour faire apparaître un chiffre (notez le ~
qui garantit que l'atome entrant a de l'énergie -1
). Ce chiffre est incrémenté de 48
pour obtenir le code ASCII du caractère correspondant. La A
fractionne le chiffre pour imprimer une copie avec!
et réintroduisez l'autre copie dans le Y
réacteur pour le chiffre suivant. La copie imprimée est utilisée comme déclencheur suivant pour la pile (notez que les miroirs l’envoient également autour du bord pour la frapper M
de la gauche).
Lorsque la pile est vide, la K
volonté reflète l'atome et convertit son énergie en une +1
telle manière qu'il passe directement à travers le S
. N
affiche une nouvelle ligne (juste parce que c'est soigné :)). Et puis, l’atome passe de R'0
nouveau pour se retrouver dans le côté de la Y
. Puisqu'il n'y a plus d'atomes autour, cela ne sera jamais publié et le programme se termine.
Calcul du nombre de fission: le cadre
Passons à la viande du programme. Le code est essentiellement un portage de mon implémentation de référence Mathematica:
fission[n_] := If[
(div =
SelectFirst[
Reverse@Divisors[2 n],
(OddQ@# == IntegerQ[n/#]
&& n/# > (# - 1)/2) &
]
) == 1,
1,
1 + Total[fission /@ (Range@div + n/div - (div + 1)/2)]
]
où div
est le nombre d'entiers dans la partition maximale.
Les principales différences sont que nous ne pouvons pas traiter les valeurs semi-entières dans Fission, je fais donc beaucoup de choses multipliées par deux et qu'il n'y a pas de récursivité dans Fission. Pour contourner ce problème, j'inspire tous les entiers d'une partition d'une file d'attente pour qu'ils soient traités ultérieurement. Pour chaque numéro que nous traitons, nous incrémentons un compteur de un et une fois la file d'attente vide, nous libérons le compteur et l'envoyons pour qu'il soit imprimé. (Une file d'attente Q
fonctionne exactement comme K
dans l'ordre FIFO.)
Voici un cadre pour ce concept:
+--- input goes in here
v
SQS ---> compute div from n D /8/
~4X | /~KSA /
3 +-----------> { +X
initial trigger ---> W 6~@/ \/
4
W ^ /
| 3
^ generate range |
| from n and div <-+----- S6
| -then-
+---- release new trigger
Les nouveaux composants les plus importants sont les chiffres. Ce sont des téléporteurs. Tous les téléporteurs avec le même chiffre vont ensemble. Lorsqu'un atome frappe un téléporteur, il déplace immédiatement le téléporteur suivant dans le même groupe, où le prochain est déterminé dans l'ordre habituel de gauche à droite et de haut en bas. Celles-ci ne sont pas nécessaires, mais aident à la mise en page (et donc au golf un peu). Il y a aussi X
qui duplique simplement un atome, en envoyant une copie tout droit et l’autre en arrière.
À ce stade, vous pourrez peut-être régler vous-même la plupart des éléments du cadre. Le coin supérieur gauche contient la file de valeurs à traiter et en libère une n
à la fois. Une copie de n
est téléportée vers le bas parce que nous en avons besoin lors du calcul de la plage, l'autre copie va dans le bloc en haut qui calcule div
(c'est de loin la plus grande section du code). Une fois div
calculé, il est dupliqué - une copie incrémente un compteur dans le coin supérieur droit, qui est stocké dans K
. L'autre copie est téléportée au bas. Si div
c'était le cas 1
, nous le renvoyons immédiatement vers le haut et l'utilisons comme déclencheur de la prochaine itération, sans mettre en attente de nouvelles valeurs. Sinon, nous utilisons div
etn
dans la section du bas pour générer la nouvelle plage (c’est-à-dire un flux d’atomes avec les masses correspondantes qui sont ensuite placées dans la file d’attente), puis relâchez un nouveau déclencheur une fois la plage complétée.
Une fois la file d'attente vide, le déclencheur sera reflété, passant directement à travers S
et réapparaissant dans le coin supérieur droit, où il relâche le compteur (le résultat final) A
, qui est ensuite téléporté vers itoa
via 8
.
Calcul du nombre de fission: le corps de boucle
Il ne reste donc que les deux sections pour calculer div
et générer la plage. L'informatique div
est cette partie:
;
{+L /$ \/\/\/\/\/ 5/ @ [~ &@[S\/ \
/A@[ %5 /; & K } [S/
\ A$@S S\/ \/\/\/ \/>\ /S]@A / \
X X /> \ +\ A\ / \ /
/ ~A\; +;\ /@
ZX [K / {/ / @ @ } \ X @
\AS </ \V / }SZS S/
X ;;@\ /;X /> \ ; X X
\@+ >/ }$S SZS\+; //\V
/ \\ /\; X X @ @ \~K{
\0X / /~/V\V / 0W//
\ Z [K \ //\
\ /\7\A /;7/\/
Vous en avez probablement déjà vu assez pour résoudre ce problème vous-même avec un peu de patience. La décomposition en haut niveau est la suivante: Les 12 premières colonnes environ génèrent un flux de diviseurs de 2n
. Les 10 prochaines colonnes filtrent celles qui ne satisfont pas OddQ@# == IntegerQ[n/#]
. Les 8 prochaines colonnes filtrent celles qui ne satisfont pas n/# > (# - 1)/2)
. Enfin, nous plaçons tous les diviseurs valides sur une pile et une fois que nous avons terminé, nous viderons toute la pile dans un réacteur à fusion (en écrasant tout le dernier / le plus grand diviseur), puis nous relâcherons le résultat, puis nous éliminerons son énergie -zéro de vérifier l'inégalité).
Il y a beaucoup de chemins fous là-dedans qui ne font rien vraiment. De manière prédominante, la \/\/\/\/
folie au sommet (les 5
s en font également partie) et un chemin autour du bas (qui passe par les 7
s). Je devais les ajouter pour faire face à de mauvaises conditions de course. La fission pourrait utiliser un composant de retard ...
Le code qui génère la nouvelle plage à partir de n
et div
est-ce:
/MJ $$\
4}K~@\ &] @\ 3/\
\{ }$A/1 2 }Y~K <\
\@ / \@<+@^ 1;}++@
2 ; \ /
Nous calculons d’abord n/div - (div + 1)/2
(les deux termes pondérés, ce qui donne le même résultat) et enregistrons pour plus tard. Ensuite, nous générons une plage allant de div
bas à 1
et ajoutons la valeur stockée à chacun d’eux.
Il y a deux nouveaux modèles communs dans ces deux, que je dois mentionner: l' un est SX
ou ZX
frappé par le bas (ou versions pivotés). C'est un bon moyen de dupliquer un atome si vous voulez qu'une copie aille droit devant vous (dans la mesure où rediriger les sorties d'un réacteur à fusion peut parfois être fastidieux). Le S
ou Z
fait pivoter l'atome dans le X
, puis fait pivoter la copie mise en miroir dans la direction de propagation d'origine.
L'autre motif est
[K
\A --> output
Si nous stockons une valeur, K
nous pouvons la récupérer à plusieurs reprises en frappant K
avec une énergie négative du haut. Les A
doublons de la valeur qui nous intéresse et renvoient quelle copie sur la pile pour la prochaine fois que nous en avons besoin.
Eh bien, c'était assez long ... mais si vous avez vraiment traversé cela, j'espère que vous avez eu l'idée que Fission est i̟nç̮̩red̙ibl̶̪̙̮̥̮y̶̠̠͎̺̪̙̮̥̮͍͍͍̱̦̰͍͜