Mini-Flak Quine le plus rapide


26

Mini-Flak est un sous - ensemble du cerveau-Flak langue, où les <>, <...>et les []opérations ne sont pas autorisés. À strictement parler, il ne doit pas correspondre à l'expression régulière suivante:

.*(<|>|\[])

Mini-Flak est le plus petit sous-ensemble complet de Turing connu de Brain-Flak.


Il y a peu de temps, j'ai pu faire une Quine en Mini-Flak , mais elle était trop lente pour fonctionner pendant la durée de vie de l'univers.

Donc, mon défi pour vous est de faire un Quine plus rapide.


Notation

Pour marquer votre code, placez un @cyindicateur à la fin de votre code et exécutez-le dans l' interpréteur Ruby ( Essayez-le en ligne utilise l'interpréteur ruby) en utilisant l' -dindicateur. Votre score doit être imprimé sur STDERR comme suit:

@cy <score>

Il s'agit du nombre de cycles que votre programme prend avant la fin et il est le même entre les exécutions. Étant donné que chaque cycle prend environ le même temps pour être exécuté, votre score doit être directement corrélé au temps nécessaire pour exécuter votre programme.

Si votre Quine est trop long pour que vous puissiez raisonnablement l'exécuter sur votre ordinateur, vous pouvez calculer le nombre de cycles à la main.

Le calcul du nombre de cycles n'est pas très difficile. Le nombre de cycles équivaut à 2 fois le nombre de monades exécutées plus le nombre de nilades exécutées. Cela revient à remplacer chaque nilad par un seul caractère et à compter le nombre de caractères exécutés au total.

Exemple de notation

  • (()()()) marque 5 car il a 1 monade et 3 nilades.

  • (()()()){({}[()])} marque 29 parce que la première partie est la même qu'avant et marque 5 tandis que la boucle contient 6 monades et 2 nilades marquant 8. La boucle est exécutée 3 fois donc nous comptons son score 3 fois. 1*5 + 3*8 = 29


Exigences

Votre programme doit ...

  • Être d'au moins 2 octets

  • Imprimer son code source lorsqu'il est exécuté dans Brain-Flak en utilisant le -Adrapeau

  • Ne correspond pas à l'expression régulière .*(<|>|\[])


Conseils

  • L' interpréteur Crane-Flak est catégoriquement plus rapide que l'interpréteur ruby ​​mais manque certaines des fonctionnalités. Je recommanderais de tester votre code à l'aide de Crane-Flak d'abord, puis de le marquer dans l'interpréteur ruby ​​lorsque vous savez que cela fonctionne. Je recommanderais également fortement de ne pas exécuter votre programme dans TIO. Non seulement TIO est plus lent que l'interpréteur de bureau, mais il expirera également dans environ une minute. Ce serait extrêmement impressionnant si quelqu'un réussissait à obtenir un score suffisamment bas pour exécuter son programme avant l'expiration du délai de TIO.

  • [(...)]{}et (...)[{}]fonctionnent de la même manière <...>que ne respectent pas l'exigence de source restreinte

  • Vous pouvez consulter Brain-Flak et Mini-Flak Quines si vous voulez avoir une idée de la façon d'aborder ce défi.


1
"actuel le meilleur" -> "actuel seulement"
HyperNeutrino

Réponses:


33

Mini-Flak, 6851113 cycles

Le programme (littéralement)

Je sais que la plupart des gens ne s'attendent pas à ce qu'un quine Mini-Flak utilise des caractères non imprimables et même des caractères multi-octets (ce qui rend l'encodage pertinent). Cependant, ce quine fait, et les non imprimables, combinés à la taille du quine (93919 caractères encodés en 102646 octets UTF-8), rendent assez difficile de placer le programme dans ce message.

Cependant, le programme est très répétitif et, en tant que tel, se comprime très bien. Pour que l'ensemble du programme soit disponible littéralement à partir de Stack Exchange, il existe un xxdvidage hexadécimal réversible d'une gzipversion compressée du quine complet caché derrière le pliable ci-dessous:

(Oui, c'est tellement répétitif que vous pouvez même voir les répétitions après avoir été compressé).

La question dit "Je recommanderais également fortement de ne pas exécuter votre programme dans TIO. Non seulement TIO est plus lent que l'interpréteur de bureau, mais il expirera également dans environ une minute. Ce serait extrêmement impressionnant si quelqu'un réussissait à obtenir un score suffisamment bas pour s'exécuter. leur programme avant TIO expiré. " Je peux le faire! Il faut environ 20 secondes pour s'exécuter sur TIO, en utilisant l'interpréteur Ruby: Essayez-le en ligne!

Le programme (lisible)

Maintenant que j'ai donné une version du programme que les ordinateurs peuvent lire, essayons une version que les humains peuvent lire. J'ai converti les octets qui composent le quine en page de code 437 (s'ils ont le bit élevé défini) ou en images de contrôle Unicode (si ce sont des codes de contrôle ASCII), ajouté des espaces blancs (tout espace blanc préexistant a été converti en images de contrôle ), codé en fonction de la longueur d'exécution à l'aide de la syntaxe «string×length», et certains bits lourds en données ont été supprimés:

␠
(((()()()()){}))
{{}
    (({})[(()()()())])
    (({})(
        {{}{}((()[()]))}{}
        (((((((({})){}){}{})){}{}){}){}())
        {
            ({}(
                (␀␀!S␠su! … many more comment characters … oq␝qoqoq)
                («()×35» («()×44» («()×44» («()×44» («()×44» («()×45»
                … much more data encoded the same way …
                («()×117»(«()×115»(«()×117»
                «000010101011┬â┬ … many more comment characters … ┬â0┬â┬à00␈␈
                )[({})(
                    ([({})]({}{}))
                    {
                        ((()[()]))
                    }{}
                    {
                        {
                            ({}(((({}())[()])))[{}()])
                        }{}
                        (({}))
                        ((()[()]))
                    }{}
                )]{}
                %Wwy$%Y%ywywy$wy$%%%WwyY%$$wy%$$%$%$%$%%wy%ywywy'×almost 241»
                ,444454545455┬ç┬ … many more comment characters … -a--┬ü␡┬ü-a␡┬ü
            )[{}()])
        }{}
        {}({}())
    )[{}])
    (({})(()()()()){})
}{}{}␊

(Le "presque 241" est dû au fait que la 241e copie n'a pas de fin ', mais est par ailleurs identique aux 240 autres.)

Explication

À propos des commentaires

La première chose à expliquer est, que se passe-t-il avec les caractères non imprimables et autres ordures qui ne sont pas des commandes Mini-Flak? Vous pensez peut-être que l'ajout de commentaires au quine rend les choses plus difficiles, mais c'est une compétition de vitesse (pas une compétition de taille), ce qui signifie que les commentaires ne nuisent pas à la vitesse du programme. Pendant ce temps, Brain-Flak, et donc Mini-Flak, vident simplement le contenu de la pile vers la sortie standard; si vous deviez vous assurer que la pile ne contenait queles personnages qui composaient les commandes de votre programme, vous auriez à passer des cycles à nettoyer la pile. En l'état, Brain-Flak ignore la plupart des caractères, aussi longtemps que nous nous assurons que les éléments de la pile de courrier indésirable ne sont pas des commandes Brain-Flak valides (ce qui en fait un polyglotte Brain-Flak / Mini-Flak), et ne sont pas négatifs ou extérieurs la plage Unicode, nous pouvons simplement les laisser sur la pile, leur permettre d'être sortis, et mettre le même caractère dans notre programme au même endroit pour conserver la propriété quine.

Il existe un moyen particulièrement important de tirer parti de cela. Le quine fonctionne en utilisant une longue chaîne de données, et fondamentalement toute la sortie du quine est produite en formatant la chaîne de données de différentes manières. Il n'y a qu'une seule chaîne de données, malgré le fait que le programme a plusieurs morceaux; nous devons donc pouvoir utiliser la même chaîne de données pour imprimer différentes parties du programme. L'astuce «données indésirables n'a pas d'importance» nous permet de le faire de manière très simple; nous stockons les caractères qui composent le programme dans la chaîne de données en ajoutant ou en soustrayant une valeur à ou de leur code ASCII. Plus précisément, les caractères composant le début du programme sont stockés en tant que leur code ASCII + 4, les caractères constituant la section qui est répétée près de 241 fois en tant que leur code ASCII - 4,chaque caractère de la chaîne de données avec un décalage; si, par exemple, nous l'imprimons avec 4 ajoutés à chaque code de caractère, nous obtenons une répétition de la section répétée, avec quelques commentaires avant et après. (Ces commentaires sont simplement les autres sections du programme, avec des codes de caractères décalés afin qu'ils ne forment pas de commandes Brain-Flak valides, car le mauvais décalage a été ajouté. Nous devons esquiver les commandes Brain-Flak, pas seulement les mini- Commandes Flak, pour éviter de violer la partie de la question; le choix des décalages a été conçu pour garantir cela.)

En raison de cette astuce de commentaire, nous devons en fait uniquement être en mesure de sortir la chaîne de données formatée de deux manières différentes: a) codées de la même manière que dans la source, b) sous forme de codes de caractères avec un décalage spécifié ajouté à chaque code. C'est une énorme simplification qui fait que la longueur ajoutée en vaut vraiment la peine.

Structure du programme

Ce programme se compose de quatre parties: l'intro, la chaîne de données, le formateur de chaîne de données et l'outro. L'intro et l'outro sont essentiellement responsables de l'exécution de la chaîne de données et de son formateur en boucle, en spécifiant à chaque fois le format approprié (c'est-à-dire s'il faut encoder ou compenser, et quel décalage utiliser). La chaîne de données n'est que des données et est la seule partie du quine pour laquelle les caractères qui la composent ne sont pas spécifiés littéralement dans la chaîne de données (cela serait évidemment impossible, car cela devrait être plus long que lui-même); il est donc écrit d'une manière particulièrement facile à régénérer à partir de lui-même. Le formateur de chaîne de données est composé de 241 parties presque identiques, chacune formatant une donnée spécifique à partir des 241 de la chaîne de données.

Chaque partie du programme peut être produite via la chaîne de données et son formateur comme suit:

  • Pour produire l'outro, formatez la chaîne de données avec le décalage +8
  • Pour produire le formateur de chaîne de données, formatez la chaîne de données avec un décalage +4, 241 fois
  • Pour produire la chaîne de données, formatez la chaîne de données via l'encodage dans le format source
  • Pour produire l'intro, formatez la chaîne de données avec le décalage -4

Donc, tout ce que nous avons à faire est de voir comment ces parties du programme fonctionnent.

La chaîne de données

(«()×35» («()×44» («()×44» («()×44» («()×44» («()×45» …

Nous avons besoin d'un encodage simple pour la chaîne de données car nous devons pouvoir inverser l'encodage en code Mini-Flak. Vous ne pouvez pas être beaucoup plus simple que cela!

L'idée clé derrière ce quine (en dehors de l'astuce de commentaire) est de noter qu'il n'y a fondamentalement qu'un seul endroit où nous pouvons stocker de grandes quantités de données: les «sommes des valeurs de retour de commande» dans les différents niveaux d'imbrication de la source du programme. (Ceci est communément appelé la troisième pile, bien que Mini-Flak n'ait pas de deuxième pile, donc "pile de travail" est probablement un meilleur nom dans le contexte Mini-Flak.) Les autres possibilités de stockage de données seraient la pile principale / première (qui ne fonctionne pas parce que c'est là que notre sortie doit aller, et nous ne pouvons pas déplacer la sortie au-delà du stockage d'une manière efficace à distance), et codée en un bignum dans un seul élément de pile (ce qui ne convient pas à ce problème car cela prend du temps exponentiel pour en extraire des données); lorsque vous les supprimez, la pile de travail est le seul emplacement restant.

Afin de "stocker" les données sur cette pile, nous utilisons des commandes asymétriques (dans ce cas, la première moitié d'une (…)commande), qui seront équilibrées dans le formateur de chaîne de données plus tard. Chaque fois que nous fermons l'une de ces commandes dans le formateur, elle va pousser la somme d'une donnée tirée de la chaîne de données et les valeurs de retour de toutes les commandes à ce niveau d'imbrication dans le formateur; nous pouvons nous assurer que ces derniers s'ajoutent à zéro, de sorte que le formateur ne voit que des valeurs uniques prises dans la chaîne de données.

Le format est très simple:, (suivi de n copies de (), où n est le nombre que nous voulons stocker. (Notez que cela signifie que nous ne pouvons stocker que des nombres non négatifs et que le dernier élément de la chaîne de données doit être positif.)

Un point légèrement peu intuitif de la chaîne de données est l'ordre dans lequel elle se trouve. Le "début" de la chaîne de données est la fin la plus proche du début du programme, c'est-à-dire le niveau d'imbrication le plus à l'extérieur; cette partie est formatée en dernier (lorsque le formateur s'étend des niveaux d'imbrication les plus internes aux plus externes). Cependant, bien qu'il soit formaté en dernier, il est imprimé en premier, car les valeurs insérées en premier dans la pile sont imprimées en dernier par l'interpréteur Mini-Flak. Le même principe s'applique au programme dans son ensemble; nous devons d'abord formater l'outro, puis le formateur de chaîne de données, puis la chaîne de données, puis l'intro, c'est-à-dire l'inverse de l'ordre dans lequel ils sont stockés dans le programme.

Le formateur de chaîne de données

)[({})(
    ([({})]({}{}))
    {
        ((()[()]))
    }{}
    {
        {
            ({}(((({}())[()])))[{}()])
        }{}
        (({}))
        ((()[()]))
    }{}
)]{}

Le formateur de chaîne de données est constitué de 241 sections qui ont chacune un code identique (une section a un commentaire légèrement différent), chacune formatant un caractère spécifique de la chaîne de données. (Nous ne pouvions pas utiliser une boucle ici: nous avons besoin d'un asymétrique )pour lire la chaîne de données en faisant correspondre son asymétrique (, et nous ne pouvons pas en mettre une dans la {…}boucle, la seule forme de boucle qui existe. Donc, à la place, nous " dérouler "le formateur et obtenir simplement l'intro / sortie pour sortir la chaîne de données avec le décalage du formateur 241 fois.)

)[({})( … )]{}

La partie la plus externe d'un élément de mise en forme lit un élément de la chaîne de données; la simplicité de l'encodage de la chaîne de données conduit à un peu de complexité dans sa lecture. Nous commençons par fermer l'inégalé (…)dans la chaîne de données, puis annulons ( […]) deux valeurs: la donnée que nous venons de lire dans la chaîne de données ( ({})) et la valeur de retour du reste du programme. Nous copions la valeur de retour du reste de l'élément de formateur avec (…)et ajoutons la copie à la version négative avec {}. Le résultat final est que la valeur de retour de l'élément de chaîne de données et de l'élément de formateur ensemble est la donnée moins la donnée moins la valeur de retour plus la valeur de retour, ou 0; cela est nécessaire pour que l'élément de chaîne de données suivant produise la valeur correcte.

([({})]({}{}))

Le formateur utilise l'élément de pile supérieur pour savoir dans quel mode il se trouve (0 = format dans le formatage de la chaîne de données, toute autre valeur = l'offset avec lequel sortir). Cependant, juste après avoir lu la chaîne de données, la donnée est au-dessus du format sur la pile, et nous voulons les inverser. Ce code est une variante plus courte du code d'échange Brain-Flak, prenant a au - dessus de b à b au - dessus de a  +  b ; non seulement il est plus court, il est aussi (dans ce cas spécifique) plus utile, car l'effet secondaire de l'ajout de b à a n'est pas problématique lorsque b est 0, et lorsque b n'est pas 0, il fait le calcul du décalage pour nous.

{
    ((()[()]))
}{}
{
    …
    ((()[()]))
}{}

Brain-Flak n'a qu'une seule structure de flux de contrôle, donc si nous voulons autre chose qu'une whileboucle, cela prendra un peu de travail. Il s'agit d'une structure "négative"; s'il y a un 0 au-dessus de la pile, il le supprime, sinon il place un 0 au-dessus de la pile. (Cela fonctionne assez simplement: tant qu'il n'y a pas de 0 au-dessus de la pile, poussez 1 - 1 à la pile deux fois; lorsque vous avez terminé, éclatez l'élément supérieur de la pile.)

Il est possible de placer du code à l'intérieur d'une structure de négation, comme on le voit ici. Le code ne s'exécutera que si le haut de la pile était différent de zéro; donc si nous avons deux structures négatives, en supposant que les deux premiers éléments de la pile ne sont pas tous les deux à zéro, ils s'annuleront l'un l'autre, mais tout code à l'intérieur de la première structure ne s'exécutera que si l'élément supérieur de la pile n'était pas nul et le code à l'intérieur la deuxième structure ne fonctionnera que si l'élément de pile supérieur était nul. En d'autres termes, c'est l'équivalent d'une instruction if-then-else.

Dans la clause "then", qui s'exécute si le format est différent de zéro, nous n'avons en fait rien à faire; ce que nous voulons, c'est pousser les données + offset vers la pile principale (afin qu'elles puissent être sorties à la fin du programme), mais elles sont déjà là. Il nous suffit donc de traiter le cas de l'encodage de l'élément de chaîne de données sous forme source.

{
    ({}(((({}())[()])))[{}()])
}{}
(({}))

Voici comment nous procédons. La {({}( … )[{}()])}{}structure doit être familière comme une boucle avec un nombre spécifique d'itérations (ce qui fonctionne en déplaçant le compteur de boucle vers la pile de travail et en le maintenant là-bas; il sera à l'abri de tout autre code, car l'accès à la pile de travail est lié à le niveau d'imbrication du programme). Le corps de la boucle est ((({}())[()])), ce qui fait trois copies de l'élément de pile supérieur et ajoute 1 au plus bas. En d'autres termes, il transforme un 40 au-dessus de la pile en 40 au-dessus de 40 au-dessus de 41, ou considéré comme ASCII, (en ((); l' exécution de ce répétitivement faire (en (()en (()()en (()()()et ainsi de suite, et est donc un moyen simple de générer notre chaîne de données ( en supposant qu'il ya un (au - dessus de la pile déjà).

Une fois que nous avons terminé la boucle, (({}))duplique le haut de la pile (de sorte qu'elle commence maintenant ((()…plutôt que (()…. Le début (sera utilisé par la prochaine copie du formateur de chaîne de données pour formater le caractère suivant (il le développera en (()(()…puis (()()(()…, et ainsi de suite, donc cela génère la séparation (dans la chaîne de données).

%Wwy$%Y%ywywy$wy$%%%WwyY%$$wy%$$%$%$%$%%wy%ywywy'

Il y a un dernier peu d'intérêt dans le formateur de chaînes de données. OK, donc c'est surtout l'outro décalé de 4 points de code vers le bas; cependant, cette apostrophe à la fin peut sembler déplacée. '(point de code 39) se transformerait en +(point de code 43), qui n'est pas une commande Brain-Flak, donc vous avez peut-être deviné qu'il est là dans un autre but.

La raison en est que le formateur de chaînes de données s'attend à ce qu'il y ait déjà un (sur la pile (il ne contient aucun littéral 40 nulle part). le'est en fait au début du bloc qui est répété pour constituer le formateur de chaîne de données, pas à la fin, donc après que les caractères du formateur de chaîne de données ont été poussés sur la pile (et que le code est sur le point de passer à l'impression de la chaîne de données lui-même), l'outro ajuste le 39 au sommet de la pile en un 40, prêt à être utilisé par le formateur (le formateur en cours d'exécution cette fois, pas sa représentation dans la source). C'est pourquoi nous avons "presque 241" copies du formateur; il manque le premier caractère de la première copie. Et ce caractère, l'apostrophe, est l'un des trois seuls caractères de la chaîne de données qui ne correspondent pas au code Mini-Flak quelque part dans le programme; c'est purement comme méthode pour fournir une constante.

L'intro et l'outro

(((()()()()){}))
{{}
    (({})[(()()()())])
    (({})(
        {{}{}((()[()]))}{}
        (((((((({})){}){}{})){}{}){}){}())
        {
            ({}(
                (␀␀!S␠su! … many more comment characters … oq␝qoqoq)
                …
            )[{}()])
        }{}
        {}({}())
    )[{}])
    (({})(()()()()){})
}{}{}␊

L'intro et l'outro sont conceptuellement la même partie du programme; la seule raison pour laquelle nous faisons une distinction est que l'outro doit être sortie avant la chaîne de données et son formateur (pour qu'elle s'imprime après eux), tandis que l'intro doit être sortie après eux (impression avant eux).

(((()()()()){}))

Nous commençons par placer deux copies de 8 sur la pile. Il s'agit du décalage pour la première itération. La deuxième copie est parce que la boucle principale s'attend à ce qu'il y ait un élément indésirable au-dessus de la pile au-dessus du décalage, laissé derrière le test qui décide s'il doit exister la boucle principale, et nous devons donc y placer un élément indésirable afin que il ne jette pas l'élément que nous voulons réellement; une copie est la façon la plus simple (donc la plus rapide de produire) de le faire.

Il y a d'autres représentations du nombre 8 qui ne sont pas plus longues que celle-ci. Cependant, lorsque vous optez pour le code le plus rapide, c'est certainement la meilleure option. D'une part, l'utilisation ()()()()est plus rapide que, disons, (()()){}parce que, bien que les deux aient 8 caractères, le premier est un cycle plus rapide, car il (…)est compté comme 2 cycles, mais ()seulement comme un. Cependant, l'enregistrement d'un cycle est négligeable par rapport à une considération beaucoup plus importante pour un : (et )a des points de code beaucoup plus faibles que {et }, donc la génération du fragment de données pour eux sera beaucoup plus rapide (et le fragment de données prendra moins de place dans le code, aussi).

{{} … }{}{}

La boucle principale. Cela ne compte pas les itérations (c'est une whileboucle, pas une forboucle, et utilise un test pour sortir). Une fois qu'il se termine, nous jetons les deux premiers éléments de la pile; l'élément supérieur est un 0 inoffensif, mais l'élément ci-dessous sera le "format à utiliser à la prochaine itération", qui (étant un décalage négatif) est un nombre négatif, et s'il y a des nombres négatifs sur la pile lorsque le Mini -Le programme Flak se ferme, l'interprète se bloque en essayant de les sortir.

Parce que cette boucle utilise un test explicite pour éclater, le résultat de ce test sera laissé sur la pile, nous le rejetons donc comme première chose que nous faisons (sa valeur n'est pas utile).

(({})[(()()()())])

Ce code pousse 4 et f  - 4 au-dessus d'un élément de pile f , tout en laissant cet élément en place. Nous calculons à l'avance le format de la prochaine itération (alors que nous avons la constante 4 à portée de main), et obtenons simultanément la pile dans le bon ordre pour les prochaines parties du programme: nous utiliserons f comme format pour cette itération, et le 4 est nécessaire avant cela.

(({})( … )[{}])

Cela enregistre une copie de f  - 4 sur la pile de travail, afin que nous puissions l'utiliser pour la prochaine itération. (La valeur de f sera toujours présente à ce moment-là, mais ce sera à un endroit gênant de la pile, et même si nous pouvions le manœuvrer au bon endroit, nous devions passer des cycles à en soustraire 4, et imprime le code pour effectuer cette soustraction. Il est beaucoup plus facile de le stocker maintenant.)

{{}{}((()[()]))}{}

Un test pour voir si le décalage est 4 (c'est-à-dire que f  - 4 est 0). Si c'est le cas, nous imprimons le formateur de chaîne de données, nous devons donc exécuter la chaîne de données et son formateur 241 fois plutôt qu'une seule fois à ce décalage. Le code est assez simple: si f  -4 est différent de zéro, remplacez le f  -4 et le 4 lui-même par une paire de zéros; puis dans les deux cas, éclatez l'élément supérieur de la pile. Nous avons maintenant un nombre au-dessus de f sur la pile, soit 4 (si nous voulons imprimer cette itération 241 fois) ou 0 (si nous voulons l'imprimer une seule fois).

(
    ((((((({})){}){}{})){}{}){}){}
    ()
)

C'est une sorte intéressante de constante Brain-Flak / Mini-Flak; la longue ligne ici représente le nombre 60. Vous pouvez être confus à l'absence de (), qui sont normalement partout dans les constantes Brain-Flak; ce n'est pas un nombre régulier, mais un chiffre de l'Église, qui interprète les nombres comme une opération de duplication. Par exemple, le chiffre de l'Église pour 60, vu ici, fait 60 copies de son entrée et les combine tous ensemble en une seule valeur; dans Brain-Flak, les seules choses que nous pouvons combiner sont des nombres réguliers, par addition, donc nous finissons par ajouter 60 copies du haut de la pile et ainsi multiplier le haut de la pile par 60.

En guise de remarque, vous pouvez également utiliser un moteur de recherche de chiffres de sous-charge, qui génère des chiffres d'église dans la syntaxe de sous-charge, pour trouver également le numéro approprié dans Mini-Flak. Les chiffres de sous-charge (autres que zéro) utilisent les opérations "dupliquer l'élément de pile supérieur" :et "combiner les deux éléments de pile supérieurs" *; Ces deux opérations existent dans Brain-Flak, de sorte que vous traduisez juste :à ), *à {}, précédez un {}, et ajouter suffisamment (au début à l' équilibre (ce qui utilise un mélange bizarre de la pile principale et la pile de travail, mais il fonctionne).

Ce fragment de code particulier utilise le numéro d'église 60 (en fait un extrait "multiplier par 60"), avec un incrément, pour générer l'expression 60 x  + 1. Donc, si nous avions un 4 à l'étape précédente, cela nous donne une valeur de 241, ou si nous avions un 0, nous obtenons simplement une valeur de 1, c'est-à-dire que cela calcule correctement le nombre d'itérations dont nous avons besoin.

Le choix de 241 n'est pas un hasard; c'était une valeur choisie pour être a) approximativement la durée à laquelle le programme se terminerait de toute façon et b) 1 plus de 4 fois un nombre rond. Les nombres ronds, 60 dans ce cas, ont tendance à avoir des représentations plus courtes en tant que chiffres de l'Église parce que vous avez plus de flexibilité dans les facteurs à copier. Le programme contient un rembourrage plus tard pour porter la longueur à 241 exactement.

{
    ({}(
        …
    )[{}()])
}{}

Il s'agit d'une boucle for, comme celle vue précédemment, qui exécute simplement le code à l'intérieur un certain nombre de fois égal au sommet de la pile principale (qu'il consomme; le compteur de boucle lui-même est stocké sur la pile de travail, mais la visibilité de qui est lié au niveau d'imbrication du programme et il est donc impossible pour autre chose que la boucle for elle-même d'interagir avec lui). Cela exécute en fait la chaîne de données et son formateur 1 ou 241 fois, et comme nous avons maintenant ajouté toutes les valeurs que nous utilisions pour notre calcul de flux de contrôle à partir de la pile principale, nous avons le format à utiliser en plus, prêt pour le formateur à utiliser.

(␀␀!S␠su! … many more comment characters … oq␝qoqoq)

Le commentaire ici n'est pas entièrement sans intérêt. D'une part, il existe quelques commandes Brain-Flak; le )à la fin est naturellement généré comme un effet secondaire de la façon dont les transitions entre les différents segments du programme fonctionnent, donc (au début a été ajouté manuellement pour l'équilibrer (et malgré la longueur du commentaire à l'intérieur, mettre un commentaire à l'intérieur une ()commande est toujours une ()commande, donc tout ce qu'elle fait est d'ajouter 1 à la valeur de retour de la chaîne de données et de son formateur, quelque chose que la boucle for ignore complètement).

Plus particulièrement, ces caractères NUL au début du commentaire ne sont clairement pas compensés par quoi que ce soit (même la différence entre +8 et -4 n'est pas suffisante pour transformer un (en NUL). Il s'agit d'un pur rembourrage pour porter la chaîne de données de 239 éléments à 241 éléments (qui se paient facilement d'eux-mêmes: il faudrait beaucoup plus de deux octets pour générer 1 contre 239 plutôt que 1 contre 241 lors du calcul du nombre d'itérations nécessaires ). NUL a été utilisé comme caractère de remplissage car il a le point de code le plus bas possible (ce qui rend le code source de la chaîne de données plus court et donc plus rapide à produire).

{}({}())

Déposez l'élément de pile supérieur (le format que nous utilisons), ajoutez 1 au suivant (le dernier caractère à sortir, c'est-à-dire le premier caractère à imprimer, de la section de programme que nous venons de formater). Nous n'avons plus besoin de l'ancien format (le nouveau format se cache sur la pile de travail); et l'incrément est inoffensif dans la plupart des cas, et change le 'à une extrémité de la représentation source du formateur de chaîne de données en un ((qui est requis sur la pile pour la prochaine fois que nous exécuterons le formateur, pour formater la chaîne de données elle-même). Nous avons besoin d'une transformation comme celle-ci dans l'outro ou l'intro, car forcer le début de chaque élément de formateur de chaîne de données le (rendrait un peu plus complexe (car nous aurions besoin de fermer (puis d'annuler son effet plus tard), etnous aurions en quelque sorte besoin de générer un extra (quelque part car nous n'avons que près de 241 copies du formateur, pas toutes les 241 (il est donc préférable qu'un personnage inoffensif comme 'celui qui manque).

(({})(()()()()){})

Enfin, le test de sortie de boucle. Le sommet actuel de la pile principale est le format dont nous avons besoin pour la prochaine itération (qui vient de revenir de la pile de travail). Cela le copie et ajoute 8 à la copie; la valeur résultante sera ignorée la prochaine fois dans la boucle. Cependant, si nous venons d'imprimer l'intro, le décalage était de -4 donc le décalage pour la "prochaine itération" sera de -8; -8 + 8 est 0, donc la boucle sortira plutôt que de continuer sur l'itération par la suite.


16

128,673,515 cycles

Essayez-le en ligne

Explication

La raison pour laquelle les quines Miniflak sont destinées à être lentes est le manque d'accès aléatoire de Miniflak. Pour contourner cela, je crée un bloc de code qui prend un nombre et renvoie une donnée. Chaque donnée représente un seul caractère comme précédemment et le code principal interroge simplement ce bloc pour chacun à la fois. Cela fonctionne essentiellement comme un bloc de mémoire à accès aléatoire.


Ce bloc de code a deux exigences.

  • Il doit prendre un nombre et sortir uniquement le code de caractère pour ce caractère

  • Il doit être facile de reproduire la table de recherche petit à petit dans Brain-Flak

Pour construire ce bloc, j'ai en fait réutilisé une méthode à partir de ma preuve que Miniflak est Turing complet. Pour chaque donnée, il y a un bloc de code qui ressemble à ceci:

(({}[()])[(())]()){(([({}{})]{}))}{}{(([({}{}(%s))]{}))}{}

Cela soustrait un du nombre au-dessus de la pile et si zéro pousse %sla donnée en dessous. Puisque chaque pièce décrémente la taille d'une unité si vous commencez par n sur la pile, vous récupérerez la nième donnée.

C'est agréable et modulaire, il peut donc être écrit facilement par un programme.


Ensuite, nous devons configurer la machine qui traduit réellement cette mémoire en source. Il se compose de 3 parties en tant que telles:

(([()]())())
{({}[(
  -Look up table-
 )]{})
 1. (({}[()])[(())]()){(([({}{})]{}))}{}{([({}{}(([{}]))(()()()()()))]{})}{}

 2. (({}[()])[(())]()){(([({}{})]{}))}{}{([({}{}
      (({}[(
      ({}[()(((((()()()()()){}){}){}))]{}){({}[()(({}()))]{}){({}[()(({}((((()()()){}){}){}()){}))]{}){({}[()(({}()()))]{}){({}[()(({}(((()()()()())){}{}){}))]{}){([(({}{}()))]{})}}}}}{}
      (({}({}))[({}[{}])])
     )]{}({})[()]))
      ({[()]([({}({}[({})]))]{})}{}()()()()()[(({}({})))]{})
    )]{})}{}

 3. (({}[()])[(())]()){(([({}{})]{}))}{}{([({}{}
     (({}(({}({}))[({}[{}])][(
     ({}[()(
      ([()](((()()[(((((((()()()){})())){}{}){}){})]((((()()()()())){}{}){})([{}]([()()](({})(([{}](()()([()()](((((({}){}){}())){}){}{}))))))))))))
     )]{})
     {({}[()(((({})())[()]))]{})}{}
     (([(((((()()()()){}){}()))){}{}([({})]((({})){}{}))]()()([()()]({}(({})([()]([({}())](({})([({}[()])]()(({})(([()](([({}()())]()({}([()](([((((((()()()())()){}){}){}()){})]({}()(([(((((({})){}){}())){}{})]({}([((((({}())){}){}){}()){}()](([()()])(()()({}(((((({}())())){}{}){}){}([((((({}))){}()){}){}]([((({}[()])){}{}){}]([()()](((((({}())){}{}){}){})(([{}](()()([()()](()()(((((()()()()()){}){}){}()){}()(([((((((()()()())){}){}())){}{})]({}([((((({})()){}){}){}()){}()](([()()])(()()({}(((((({}){}){}())){}){}{}(({})))))))))))))))))))))))))))))))))))))))))))))))
     )]{})[()]))({()()()([({})]{})}{}())
    )]{})}{}

   ({}[()])
}{}{}{}
(([(((((()()()()){}){}())){}{})]((({}))([()]([({}())]({}()([()]((()([()]((()([({})((((()()()()){}){}()){})]()())([({})]({}([()()]({}({}((((()()()()()){}){}){}))))))))))))))))))

La machine se compose de quatre parties qui sont exécutées dans l'ordre commençant par 1 et se terminant par 3. Je les ai étiquetées dans le code ci-dessus. Chaque section utilise également le même format de table de recherche que j'utilise pour l'encodage. C'est parce que le programme entier est contenu dans une boucle et nous ne voulons pas exécuter chaque section à chaque fois que nous parcourons la boucle, nous mettons donc dans la même structure RA et interrogons la section que nous désirons à chaque fois.

1

La section 1 est une section de configuration simple.

Le programme indique les premières requêtes section 1 et datum 0. Le datum 0 n'existe pas, donc au lieu de renvoyer cette valeur, il décrémente simplement la requête une fois pour chaque datum. Ceci est utile car nous pouvons utiliser le résultat pour déterminer le nombre de données, qui deviendront importantes dans les prochaines sections. La section 1 enregistre le nombre de données en annulant le résultat et interroge la section 2 et la dernière donnée. Le seul problème est que nous ne pouvons pas interroger directement la section 2. Puisqu'il reste un décrément, nous devons interroger une section inexistante 5. En fait, ce sera le cas chaque fois que nous interrogerons une section dans une autre section. Je vais ignorer cela dans mon explication cependant si vous cherchez un code, rappelez-vous juste 5 signifie revenir en arrière une section et 4 signifie réexécuter la même section.

2

La section 2 décode les données en caractères qui composent le code après le bloc de données. Chaque fois qu'il s'attend à ce que la pile apparaisse comme suit:

Previous query
Result of query
Number of data
Junk we shouldn't touch...

Il mappe chaque résultat possible (un nombre de 1 à 6) à l'un des six caractères Miniflak valides ( (){}[]) et le place sous le nombre de données avec le "Junk nous ne devrions pas toucher". Cela nous donne une pile comme:

Previous query
Number of data
Junk we shouldn't touch...

À partir d'ici, nous devons interroger la prochaine donnée ou si nous les avons tous interrogés, passer à la section 3. La requête précédente n'est pas réellement la requête exacte envoyée mais plutôt la requête moins le nombre de données dans le bloc. Cela est dû au fait que chaque donnée décrémente la requête d'une unité, de sorte que la requête est assez tronquée. Pour générer la requête suivante, nous ajoutons une copie du nombre de données et soustrayons une. Maintenant, notre pile ressemble à:

Next query
Number of data
Junk we shouldn't touch...

Si notre prochaine requête est nulle, nous avons lu toute la mémoire nécessaire dans la section 3, donc nous ajoutons à nouveau le nombre de données à la requête et giflons un 4 au-dessus de la pile pour passer à la section 3. Si la requête suivante n'est pas nulle, nous mettez un 5 sur la pile pour relancer la section 2.

3

La section 3 crée le bloc de données en interrogeant notre RAM comme le fait la section 3.

Par souci de concision, je vais omettre la plupart des détails sur le fonctionnement de la section 3. Il est presque identique à la section 2, sauf qu'au lieu de traduire chaque donnée en un seul caractère, il se traduit chacun en un long morceau de code représentant son entrée dans la RAM. Lorsque la section 3 est terminée, elle indique au programme de quitter la boucle.


Une fois la boucle exécutée, le programme n'a plus qu'à pousser le premier bit de la quine ([()]())(()()()()){({}[(. Je le fais avec le code suivant implémentant des techniques de complexité Kolmogorov standard.

(([(((((()()()()){}){}())){}{})]((({}))([()]([({}())]({}()([()]((()([()]((()([({})((((()()()()){}){}()){})]()())([({})]({}([()()]({}({}((((()()()()()){}){}){}))))))))))))))))))

J'espère que c'était clair. Veuillez commenter si vous êtes confus à propos de quoi que ce soit.


Combien de temps cela prend-il pour fonctionner? Il est temps sur TIO.
Pavel

@Pavel Je ne l'exécute pas sur TIO car ce serait incroyablement lent, j'utilise le même interpréteur que TIO utilise (celui de rubis ). Il faut environ 20 minutes pour fonctionner sur un ancien serveur rack auquel j'ai accès. Cela prend environ 15 minutes dans Crain-Flak, mais Crain-Flak n'a pas d'indicateur de débogage, donc je ne peux pas le marquer sans l'exécuter dans l'interpréteur Ruby.
Wheat Wizard du

@ Pavel Je l'ai relancé et l'ai chronométré. Il a fallu 30m45.284sterminer sur un serveur plutôt bas de gamme (à peu près l'équivalent d'un bureau moderne moyen) en utilisant l'interpréteur ruby.
Wheat Wizard
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.