EDIT: Comme certains d'entre vous le soupçonnaient, il y avait un bug dans l'interprète officiel: l'ordre de composition .
était inversé. J'avais deux versions de l'interprète et j'ai utilisé la mauvaise ici. Les exemples ont également été écrits pour cette version incorrecte. J'ai corrigé l'interpréteur dans le référentiel et les exemples ci-dessous. La description de >
était également un peu ambiguë, j'ai donc corrigé cela. De plus, je m'excuse d'avoir pris si longtemps, j'ai été pris dans des trucs réels.
EDIT2: Mon interprète avait un bug dans l'implémentation .
qui se reflétait dans les exemples (ils s'appuyaient sur un comportement non défini). Le problème est désormais résolu.
introduction
Shift est un langage de programmation fonctionnel ésotérique que j'ai créé il y a quelques années, mais que j'ai publié aujourd'hui. Il est basé sur la pile, mais possède également un curry automatique comme Haskell.
spécification
Il existe deux types de données dans Shift:
- Fonctions, qui ont une arité positive arbitraire (nombre d'entrées), et qui renvoient une liste de sorties. Par exemple, une fonction qui duplique sa seule entrée a l'arité 1 et une fonction qui échange ses deux entrées a l'arité 2.
- Les blancs, qui sont tous identiques et n'ont d'autre but que de ne pas être des fonctions.
Un programme Shift se compose de zéro ou plusieurs commandes , dont chacune est un seul caractère ASCII. Il y a 8 commandes au total:
!
( appliquer ) extrait une fonctionf
et une valeurx
de la pile et s'appliquef
àx
. Sif
a l'arité 1, la listef(x)
est ajoutée au début de la pile. S'il a de l'aritén > 1
, une nouvelle(n-1)
fonction -aryg
est poussée dans la pile. Il faut des entrées et des retours .x1,x2,...,xn-1
f(x,x1,x2,...,xn-1)
?
( vide ) pousse un blanc dans la pile.+
( clone ) pousse dans la pile une fonction unaire qui duplique son entrée: toute valeurx
est mappée[x,x]
.>
( shift ) pousse dans la pile une fonction unaire qui prend unen
fonction -aryf
, et retourne une(n+1)
fonction -aryg
qui ignore son premier argumentx
, appellef
les autres et claquex
devant le résultat. Par exemple,shift(clone)
est une fonction binaire qui prend des entréesa,b
et des retours[a,b,b]
./
( fork ) pousse dans la pile une fonction ternaire qui prend trois entréesa,b,c
, et retourne[b]
sia
est un blanc, et[c]
sinon.$
( Appel ) pousse sur la pile une fonction binaire qui apparaît une fonctionf
et une valeurx
, et l' appliquef
àx
exactement comme le!
fait..
( chaîne ) pousse dans la pile une fonction binaire qui fait apparaître deux fonctionsf
etg
, et renvoie leur composition: une fonctionh
qui a la même arité quef
, et qui prend ses entrées normalement, leur s'appliquef
, puis s'applique entièrementg
au résultat (appels autant de fois que son arité le dicte), avec les éléments inutilisés de la sortie def
rester dans le résultat deh
. Par exemple, supposons qu'ilf
s'agit d'une fonction binaire qui clone son deuxième argument et qu'elleg
soit appelée . Si la pile contient[f,g,a,b,c]
et nous le faisons.!!
, alors elle contient[chain(f,g),a,b,c]
; si nous faisons!!
ensuite,f
est d'abord appliqué àa,b
, produisant[a,b,b]
,g
est ensuite appliqué aux deux premiers éléments de celui-ci puisque son arité est 2, produisant[a(b),b]
, et la pile sera finalement[a(b),b,c]
.@
( disons ) pousse une fonction unaire qui retourne simplement son entrée, et affiche0
s'il s'agissait d'un blanc et1
s'il s'agissait d'une fonction.
Notez que toutes les commandes, sauf !
simplement pousser une valeur dans la pile, il n'y a aucun moyen d'effectuer une entrée, et la seule façon de sortir quoi que ce soit est d'utiliser @
. Un programme est interprété en évaluant les commandes une par une, en imprimant 0
s ou 1
s chaque fois que "say" est appelé et en quittant. Tout comportement non décrit ici (appliquer un blanc, appliquer une pile de longueur 0 ou 1, appeler "chaîne" sur un blanc, etc.) n'est pas défini: l'interpréteur peut se bloquer, échouer en silence, demander une entrée, ou autre chose.
La tâche
Votre tâche consiste à écrire un interprète pour Shift. Il doit extraire de STDIN, de la ligne de commande ou de l'argument de fonction un programme Shift à interpréter et l'imprimer vers STDOUT ou renvoyer la sortie résultante (éventuellement infinie) de 0
s et 1
s. Si vous écrivez une fonction, vous devez pouvoir accéder aux sorties de longueur infinie d'une certaine manière (générateur en Python, liste paresseuse en Haskell, etc.). Alternativement, vous pouvez prendre une autre entrée, un nombre n
, et renvoyer au moins des n
caractères de la sortie si elle est plus longue que n
.
Le nombre d'octets le plus bas gagne et les failles standard sont interdites.
Cas de test
Ce programme Shift imprime 01
:
?@!@@!
En partant de la gauche: appuyez sur un blanc, appuyez sur dire , puis appliquez le mot sur le blanc. Cela sort 0
. Ensuite, appuyez deux fois sur say et appliquez le deuxième say au premier. Cela sort 1
.
Ce programme boucle pour toujours, ne produisant aucune sortie:
$+.!!+!!
Poussez call et clone , puis appliquez-leur une chaîne (nous avons besoin de deux !
s car la chaîne est une fonction binaire). Maintenant, la pile contient une fonction qui prend un argument, le duplique et appelle la première copie sur le second. Avec +!!
, nous dupliquons cette fonction et l'appelons sur elle-même.
Ce programme imprime 0010
:
?@$.++>!.!!.!!.!!!!+?/!!!@!@>!!!
Poussez un blanc et dites . Ensuite, composez une fonction binaire qui copie son deuxième argument b
, puis copie le premier a
et le compose avec lui-même, puis applique la composition à la copie de b
, en retournant [a(a(b)),b]
. Appliquez-le à dire et vide, puis appliquez dire aux deux éléments restants sur la pile.
Ce programme s'imprime 0
. Pour chacun !!!
que vous y ajoutez, il en imprime un supplémentaire 0
.
?@+$>!>!+>!///!!>!>!.!!.!!.!!+!!!!
Poussez un blanc et dites . Ensuite, composez une fonction ternaire qui prend f,g,x
en entrée et en retour [f,f,g,g(x)]
. Clonez cette fonction et appliquez-la à elle-même, disons , et au blanc. Cette application ne modifie pas la pile, nous pouvons donc appliquer à nouveau la fonction autant de fois que nous le souhaitons.
Ce programme imprime la séquence infinie 001011011101111...
, où le nombre de 1
s augmente toujours de un:
@?/!@>!??/!!>!+.!!.!!.!!.+>!.!!$$$$+$>!>!$>!>!+>!$>!>!>!+>!>!///!!>!>!>!.!!.!!.!!.!!.!!.!!.!!.!!.!!.!!+!!!!!
Le référentiel contient une version annotée.
la source
f(x1, x2, ..., xn)
etg(y1, y2, ..., ym)
. L'appel.
apparaît tous les deux et pousse une fonctionh(z1, z2, ..., zn)
. Maintenant, vous pouvez manger tous ces arguments en les étirant progressivement avec!
. Après den
telles applications, la fonction restante n'avait qu'un seul argument, et à ce stade, elle calculef(z1, z2, ..., zn)
(c'est-f
à- dire qu'elle s'applique à tous les arguments que vous avez analysés), ce qui pousse de nouvelles valeurs, puis consomme immédiatement lesm
valeurs de la pile et les appelleg
..
fonctionne exactement comme Martin l'a décrit, sauf que sif
renvoie une liste dem
valeurs inférieures à , le résultat n'est pas défini (la composition a une aritén
, donc elle ne peut pas manger plus d'arguments de la pile). Essentiellement, la sortie def
est utilisée comme une pile temporaire, sur laquelleg
sont poussées et appliquées desm
temps en utilisant!
, et le résultat est ajouté à la pile principale.Réponses:
Python 2,
752667534506445436427404398393 octetsCe n'est en aucun cas court ... mais j'ai fait de mon mieux. Toute suggestion de golf serait très appréciée ...
EDIT6: Il s'agit maintenant d'un script au lieu d'une fonction. Enregistrez-le dans un fichier (shift.py, forex), puis exécutez-le avec
$ python shift.py '<my_input>'
. Assurez-vous de mettre l'entrée entre guillemets simples, ou bash deviendra fou avec les caractères saisis.EDIT7: Aaaaaaand ... ce n'est plus lisible. Mais je me suis débarrassé de 23 octets de plus, donc c'est bien, je suppose? Je publierai également une version non golfée.
EDIT8: Encore un golf, grâce à @Zgarb.
EDIT: merci à @DLosc pour l'aide au golf! Géré pour le réduire de 85 octets.
EDIT2: coupez une tonne de wrappers inutiles et laissez-la tomber de 133 octets!
EDIT3: ... et 28 de plus grâce à @ Sp3000 et @orlp dans le chat!
EDIT4: avec l'aide de @orlp & @ Sp3000, tous les décorateurs ont été supprimés et il est désormais plus court de 61 octets.
EDIT5: aidez-moi, je ne peux pas arrêter de jouer au golf .... 9 octets de plus sont partis. Se débarrasser de l'instruction d'impression finale économiserait 7 autres, mais si vous exécutez m () dans une boucle, toute la sortie est sur la même ligne ... est-ce correct?
Voici une version non golfée:
L'idée de base est que la liste python fonctionne très bien comme une pile, et en stockant
u=k.append
, non seulement j'enregistre des caractères,mais je peux également l'utiliser(plus maintenant!).@u
comme décorateur pour pousser des fonctionsÉtant donné que le couple de fonctions qui agissent sur les fonctions de n-aire doit pouvoir accepter un nombre arbitraire d'arguments, j'ai dû utiliser
*args
, ce qui signifiait que mon plan d'origine de suivi f.func_code.co_argcount devait être remplacé par une arité attributdécorateur.En termes de gestion de programmes infinis, l'interpréteur s'exécute jusqu'à ce qu'il atteigne la profondeur récursive maximale; le gestionnaire RuntimeError en bas le fait sortir tranquillement à ce point, et il imprime ensuite la chaîne de sortie actuelle.
Cas de test:
la source
['1','0'][...]
par juste'10'[...]
. 3) Pourquoix is 0
et nonx==0
(oux<1
)? 4) Ne vous embêtez pas à préciserRuntimeError
,except
ça suffira. 5) Puisque vous utilisez Python 2, les tabulations et les espaces comptent comme différents niveaux d'indentation - en gros, mais devraient vous faire économiser environ 25 octets.x.a==1and x(y)or u(a(x.a-1)(b.partial(x,y)))
- les opérateurs logiques sont toujours en court-circuit, mais utilisent moins de caractères que le ternaire. Ensuite , enregistrez un autre octet en utilisantx.a-1
comme condition (0 / false six
est 1, non nulle / true autrement) et l' échange de la « puis » et « autre » expressions:x.a-1and u(a(x.a-1)(b.partial(x,y)))or x(y)
. (Je dois jouer au golf maintenant que vous m'avez dépassé ...; ^))x.a==1
c'est vrai maisx(y)
renvoie quelque chose de faux, il essaie également d'évalueru(...)
. Mais il semble que vous n'ayez pas besoin de sauvegarder les 3 octets minables qui vous auraient donné! Je concède, monsieur: vous m'avez dépassé.RuntimeError
temps que le mien demande simplement à l'utilisateur de rediriger stderr ... nous sommes donc probablement même en jeu. ; ^)*_
servent les lambdas?Ghostscript
Pas encore joué au golf, car j'ai encore besoin de travailler sur la fonction d'analyse.
Cette implémentation utilise
_
et:
au lieu de>
et/
, et elle nécessite que tous les caractères du programme soient séparés par des espaces. En effet>
,/
les noms ne sont pas valides dans Postscript et les opérateurs ne se délimitent pas d'eux-mêmes, mais cela sera résolu lorsque j'écrirai l'analyseur.La première partie du code doit être assez transparente, car elle ne fait que répéter les définitions des fonctions opérateur. La magie opère dans la définition de
!
.La façon dont
!
fonctionne est simple: Tout d' abord, il ajoute l' argumentx
àf
en préfixantx
au contenuf
, en le poussant en arrière sur la pile, et en nommant une copie du résultatfun
.Il enveloppe ensuite la pile entière sous forme de tableau.
.runandhide
est une extension Ghostscript pour exécuter du code en bac à sable, cachant le contenu du tableau précédent de la procédure sur laquelle il est invoqué. Ladict
commande pousse un nouveau dictionnaire sur la pile de dict, réduisant la portée des noms définis dans jusqu'à ceend
qu'il revienne. Il remplace également=only
(l'opérateur de sortie que j'utilise dans@
) par un faux, supprimant la sortie pendant l'essai.stopped
est l'équivalent PostScript de l'try
instruction trouvée dans d'autres langages et renvoie true si sa procédure a généré une erreur, et false si elle s'est terminée.Une fois l'exécution d'essai
fun
terminée, le programme restaure la pile d'origine à partir du tableau caché et, s'il estfun
terminé sans erreur, il l'exécute pour de vrai, en conservant la sortie.la source
Python3,
685670634633 octetsJe suis presque sûr que c'est la plus longue chose que j'ai jamais jouée. Auparavant, il était quelque peu lisible, mais suivre les conseils de @ sirpercival a éliminé cet inconvénient!
k
est la pile, qui contient des fonctions représentées comme des chaînes comme"h(0,0)"
(qui est c h ain ). Lorsqu'une fonction est passée en argument à une autre fonction, elle obtientrepr
« d et tous les numéros incrémentée:"h('h(1,1)',0)"
. Une fois que tous les0
s sont remplacés dans une fonction, le tout est passé àeval
, appelant ainsi la fonction Python appropriée - dont la plupart sont des fonctions lambda générées à partir de la grande chaîne de la ligne 6 par laexec
ligne 7.Obtenir plusieurs niveaux de fonctions imbriquées incrémentées, citées et échappées correctement était le plus gros casse-tête. Je pourrais économiser un peu plus sur les opérations regex si je pouvais supposer que l'imbrication de fonctions ne se poursuivra pas plus profondément que 9 niveaux, mais comme indiqué dans les commentaires, ce n'est probablement pas une hypothèse sûre.
Version antérieure du code non golfée:
Le seul défaut potentiel de cette implémentation est qu'elle utilise la récursivité, donc les programmes qui devraient être infinis atteignent assez rapidement la profondeur de récursivité maximale. (Vous voudrez probablement rediriger stderr lorsque vous exécutez un programme infini - sinon la trace de la pile submergera la sortie réelle.) À part cela, tout semble fonctionner.
la source
lambda a
etk.pop()
.k.pop()
situation un peu moins répétitive, de toute façon.)Ceylan,
116710571031Je ne suis pas aussi court que les versions python mono-typées ...
import ceylon.language.meta.model{N=Function}import ceylon.collection{H=HashMap}interface D of F|b{}object b satisfies D{}class F(shared Integer a,[D+](D+)f,[D*]c=[])satisfies D{shared[D+]o(D i){[D+]s=[i].prepend(c);return a==1then f(*s)else[F(a-1,f,s)];}shared[D+]y([D+]i){return f(*i.prepend(c));}}F m<A>(N<[D+],A>f)given A satisfies[D+]=>F(f.parameterTypes.size,(D+i)=>f.apply(*i));[D,D]e(D x)=>[x,x];[F]t(F f){[D+]g(D+i){assert(is[D+]r=i.rest);return[i[0],*f.y(r)];}return[F(f.a+1,g)];}[D]k(D a,D d,D c)=>a==b then[d]else[c];[D+]l(F a,D x)=>a.o(x);[F]n(F f,F g){[D+]h(D+i){[D+]r=f.y(i);assert(is[D+]d=r[0:g.a]);return g.y(d).append(r[g.a...]);}return[F(f.a,h)];}[D]y(D x){process.write(x==b then"0"else"1");return[x];}class I(){variable D[]s=[];value c=H{'?'->b,'+'->m(`e`),'>'->m(`t`),'/'->m(`k`),'$'->m(`l`),'.'->m(`n`),'@'->m(`y`)};shared void r(Character i){if(i=='!'){assert(is F f=s[0],is D x=s[1]);s=f.o(x).append(s[2...]);}else{assert(is D d=c[i]);s=[d].append(s);}}}shared void z(){process.readLine()?.collect(I().r);}
Voici une version formatée (et commentée) du même code (avec les espaces / nouvelles lignes / commentaires, elle devient 4867 octets):
Les fonctions clone
e
, shiftt
, forkk
, calll
, sayy
et chainn
utilisent la dernière lettre des noms pour la version abrégée, car cela a donné moins de collisions. (Anecdote: fourchette a été définie ainsi:[Data] fork(Data a, Data b, Data c) => a == blank then [b] else [c];
- quand je renommeblank
àb
ce cassé, parce que maintenant comparé les paramètresa
et lab
placea
avec le blanc m'a pris du temps à déboguer..)La
z
fonction est partagée car mon IDE exécute ces fonctions - l'outil de ligne de commande peut également exécuter des fonctions non partagées.la source