Ottimizzazione del compilatore SKI


22

Il calcolo SKI è una variante del calcolo Lambda che non utilizza espressioni lambda. Invece, solo l'applicazione e le combinatori S , K e I vengono utilizzati. In questa sfida, il tuo compito è tradurre i termini SKI in termini Lambda in forma β normale .


Specifica di input

L'input è un termine SKI nella seguente rappresentazione testuale. Puoi scegliere di ricevere una nuova riga finale facoltativa. L'ingresso è composto dai caratteri S, K, I, (, e )e soddisfa la seguente grammatica (in forma ABNF) con stermessendo il simbolo iniziale:

sterm = sterm combinator     ; application
sterm = combinator           ;
sterm = '(' sterm ')'        ; grouping
combinator = 'S' | 'K' | 'I' ; primitives

Specifiche di uscita

L'output è un termine lambda senza variabili libere nella seguente rappresentazione testuale. Puoi scegliere di produrre una nuova riga finale opzionale. L'output deve soddisfare la seguente grammatica in forma ABNF con ltermil simbolo iniziale:

lterm   = lterm operand     ; application
lterm   = ALPHA '.' lterm   ; lambda
lterm   = operand
operand = '(' lterm ')'     ; grouping
operand = ALPHA             ; variable (a letter)

vincoli

Si può presumere che l'input abbia una forma β normale. Si può presumere che la forma normale β usi al massimo 26 variabili diverse. Si può presumere che sia l'input che l'output siano rappresentabili in 79 caratteri.

Ingressi campione

Ecco un paio di input di esempio. L'uscita è un'uscita possibile per l'ingresso specificato.

input                        output
I                            a.a
SKK                          a.a
KSK                          a.b.c.ac(bc)
SII                          a.aa

punteggio

Vince la soluzione più corta in ottetti. Sono vietate le scappatoie comuni.


7
+1 perché presumo sia una bella sfida; Non ne ho capito una parola.
Alex A.

2
Ah, dovrei giocare a golf su ski.aditsu.net :)
aditsu,

Probabilmente dovresti dichiarare che entrambi sterme ltermusare l'associatività di sinistra quando mancano le parentesi.
Peter Taylor,

@PeterTaylor Meglio così?
FUZxxl,

No, penso che sia effettivamente sbagliato: seguendo quella grammatica modificata analizzerei SKIcome S(KI).
Peter Taylor,

Risposte:


3

Haskell , 232 byte

data T s=T{a::T s->T s,(%)::s}
i d=T(i. \x v->d v++'(':x%v++")")d
l f=f`T`\v->v:'.':f(i(\_->[v]))%succ v
b"S"x=l$l.(a.a x<*>).a
b"K"x=l(\_->x)
b"I"x=x
p?'('=l id:p
(p:q:r)?')'=a q p:r
(p:q)?v=a p(l$b[v]):q
((%'a')=<<).foldl(?)[l id]

Provalo online!

Come funziona

Questo è un frontend del parser diverso per la mia risposta a "Scrivi un interprete per il calcolo lambda non tipizzato" , che ha anche una versione non golfata con documentazione.

In breve, Term = T (Char -> String)è il tipo di termini di calcolo lambda, che sanno come applicarsi ad altri termini ( a :: Term -> Term -> Term) e come mostrarsi come String( (%) :: Term -> Char -> String), data una variabile iniziale iniziale come a Char. Possiamo convertire una funzione in termini in termini con l :: (Term -> Term) -> Term, e poiché l'applicazione del termine risultante chiama semplicemente la funzione ( a (l f) == f), i termini vengono automaticamente ridotti alla forma normale quando visualizzati.


9

Rubino, 323 byte

Non riesco a credere che questo pezzo di merda funzioni affatto:

h={};f=96;z=gets.chop
{?S=>'s0.t0.u0.s0u0(t0u0)',?K=>'k0.l0.k0',?I=>'i0.i0'}.each{|k,v|z.gsub!k,?(+v+?)}
loop{z=~/\((?<V>\w1*0)\.(?<A>(?<B>\w1*0|[^()]|\(\g<B>+\))+)\)(?<X>\g<B>)/
s=$`;t=$';abort z.gsub(/\w1*0/){|x|h[x]=h[x]||(f+=1).chr}if !t
z=$`+?(+$~[?A].gsub($~[?V],$~[?X].gsub(/\w1*0/){|a|s[a]?a:a.gsub(?0,'10')})+?)+t}

L'uso della sostituzione regex per eseguire la riduzione del β su stringhe grezze è una cosa di Tony-the-Pony. Tuttavia, il suo output sembra corretto almeno per semplici test:

$ echo 'I' | ruby ski.rb
(a.a)
$ echo 'SKK' | ruby ski.rb
(a.(a))
$ echo 'KSK' | ruby ski.rb
((a.b.c.ac(bc)))
$ echo 'SII' | ruby ski.rb
(a.(a)((a)))

Eccone la gestione K(K(K(KK)))con alcuni output di debug, che impiegano circa 7 secondi sul mio laptop, perché la ricorsione delle espressioni regolari è lenta . Puoi vedere la sua conversione α in azione!

$ echo 'K(K(K(KK)))' | ruby ski.rb
"(l0.((k10.l10.k10)((k10.l10.k10)((k10.l10.k10)(k10.l10.k10)))))"
"(l0.((l10.((k110.l110.k110)((k110.l110.k110)(k110.l110.k110))))))"
"(l0.((l10.((l110.((k1110.l1110.k1110)(k1110.l1110.k1110)))))))"
"(l0.((l10.((l110.((l1110.(k11110.l11110.k11110))))))))"
(a.((b.((c.((d.(e.f.e))))))))

Ottengo: ski.rb: 4: in `gsub ': argomento errato tipo nil (previsto Regexp) (TypeError) con l'esempio' I '
aditsu,

Ora dovrebbe essere risolto! L'avevo già corretto localmente, ma ho dimenticato di modificare il mio post.
Lynn,

2
Ok, è s ........ l ....................... o ........... w, ma sembra funzionare .... alla fine :) Penso che il risultato per S (K (SI)) K non sia corretto però.
aditsu,

9

Python 2, 674

exec u"""import sys
$ V#):%=V.c;V.c+=1
 c=97;p!,v,t:[s,t.u({})][v==s];r!:s;u!,d:d.get(s,s);s!:chr(%)
 def m(s,x):%=min(%,x);-(%==x)+x
$ A#,*x):%,&=x
 C='()';p!,x,y:s.__$__(%.p/,&.p/);m!,x:&.m(%.m(x));u!,d:A(%.u(d),&.u(d));s!:%.s()+s.C[0]+&.s()+s.C[1:]
 def r(s):x=%.r();y=&.r();-x.b.p(x.a,y).r()if'L'in`x`else s.__$__/
$ L(A):C='.';u!,d:L(d.setdefault(%,V()),&.u(d))
x=V();y=V();z=V()
I=L(x,x)
K=L(y,L/)
S=L(x,L(z,L(y,A(A/,A(z,y)))))
def E():
 t=I
 while 1:
    q=sys.stdin.read(1)
    if q in')\\n':-t
    t=A(t,eval(max(q,'E()')).u({}))
t=E().r()
t.m(97)
print t.s()""".translate({33:u'=lambda s',35:u':\n def __init__(s',36:u'class',37:u's.a',38:u's.b',45:u'return ',47:u'(x,y)'})

Nota: dopo while 1:, 3 righe sono rientrate con un carattere di tabulazione.

Questo è fondamentalmente il codice dietro http://ski.aditsu.net/ , tradotto in python, molto semplificato e fortemente golfato.

Riferimento: (questo è probabilmente meno utile ora che il codice è compresso)

V = termine variabile
A = termine applicativo
L = termine lambda
c = contatore variabile
p = sostituisci variabile con termine
r = riduci
m = rinumerazione variabile finale
u = rinumerazione variabile interna (per termini duplicati)
s = conversione stringa
(parametro s = auto)
C = caratteri separatori per la conversione di stringhe
I, K, S: combinatori
E = analisi

Esempi:

python ski.py <<< "KSK"
a.b.c.a(c)(b(c))
python ski.py <<< "SII"        
a.a(a)
python ski.py <<< "SS(SS)(SS)"
a.b.a(b)(c.b(c)(a(b)(c)))(a(d.a(d)(e.d(e)(a(d)(e))))(b))
python ski.py <<< "S(K(SI))K" 
a.b.b(a)
python ski.py <<< "S(S(KS)K)I"                   
a.b.a(a(b))
python ski.py <<< "S(S(KS)K)(S(S(KS)K)I)"        
a.b.a(a(a(b)))
python ski.py <<< "K(K(K(KK)))"
a.b.c.d.e.f.e
python ski.py <<< "SII(SII)"
[...]
RuntimeError: maximum recursion depth exceeded

(questo ↑ è previsto perché SII(SII)è irriducibile)

Grazie Mauris e Sp3000 per l'aiuto nell'uccidere un mucchio di byte :)


1
Sono abbastanza sicuro che si può trasformare def m(a,b,c):return fooin m=lambda a,b,c:fooanche all'interno delle classi, che si potrebbe risparmiare un sacco di byte.
Lynn,

@Mauris grazie per la punta :)
aditsu,

Non riesco a leggere a.b.c.a(c)(b(c))come espressione lambda valida: che cos'è (c)?
coredump,

@coredump è un operando con raggruppamenti inutili ... e hai ragione, non corrisponde esattamente alle regole grammaticali del PO. Mi chiedo quanto sia importante; Chiederò.
aditsu,

@coredump Ora dovrebbe andare bene con la grammatica aggiornata.
aditsu,

3

Lisp comune, 560 byte

"Finalmente, ho trovato un uso per PROGV."

(macrolet((w(S Z G #1=&optional(J Z))`(if(symbolp,S),Z(destructuring-bind(a b #1#c),S(if(eq a'L),G,J)))))(labels((r(S #1#(N 97))(w S(symbol-value s)(let((v(make-symbol(coerce`(,(code-char N))'string))))(progv`(,b,v)`(,v,v)`(L,v,(r c(1+ n)))))(let((F(r a N))(U(r b N)))(w F`(,F,U)(progv`(,b)`(,U)(r c N))))))(p()(do((c()(read-char()()#\)))q u)((eql c #\))u)(setf q(case c(#\S'(L x(L y(L z((x z)(y z))))))(#\K'(L x(L u x)))(#\I'(L a a))(#\((p)))u(if u`(,u,q)q))))(o(S)(w S(symbol-name S)(#2=format()"~A.~A"b(o c))(#2#()"~A(~A)"(o a)(o b)))))(lambda()(o(r(p))))))

Ungolfed

;; Bind S, K and I symbols to their lambda-calculus equivalent.
;;
;; L means lambda, and thus:
;;
;; -  (L x S) is variable binding, i.e. "x.S"
;; -  (F x)   is function application

(define-symbol-macro S '(L x (L y (L z ((x z) (y z))))))
(define-symbol-macro K '(L x (L u x)))
(define-symbol-macro I '(L x x))

;; helper macro: used twice in R and once in O

(defmacro w (S sf lf &optional(af sf))
  `(if (symbolp ,S) ,sf
       (destructuring-bind(a b &optional c) ,S
         (if (eq a 'L)
             ,lf
             ,af))))

;; R : beta-reduction

(defun r (S &optional (N 97))
  (w S
      (symbol-value s)
      (let ((v(make-symbol(make-string 1 :initial-element(code-char N)))))
        (progv`(,b,v)`(,v,v)
              `(L ,v ,(r c (1+ n)))))
      (let ((F (r a N))
            (U (r b N)))
        (w F`(,F,U)(progv`(,b)`(,U)(r c N))))))

;; P : parse from stream to lambda tree

(defun p (&optional (stream *standard-output*))
  (loop for c = (read-char stream nil #\))
        until (eql c #\))
        for q = (case c (#\S S) (#\K K) (#\I I) (#\( (p stream)))
        for u = q then `(,u ,q)
        finally (return u)))

;; O : output lambda forms as strings

(defun o (S)
  (w S
      (princ-to-string S)
      (format nil "~A.~A" b (o c))
      (format nil (w b "(~A~A)" "(~A(~A))") (o a) (o b))))

Beta-riduzione

Le variabili vengono legate dinamicamente durante la riduzione con i PROGVnuovi simboli Common Lisp, usando MAKE-SYMBOL. Ciò consente di evitare piacevolmente le collisioni di denominazione (ad esempio l'ombreggiamento indesiderato delle variabili associate). Avrei potuto usare GENSYM, ma vogliamo avere nomi intuitivi per i simboli. Ecco perché i simboli sono nominati con lettere da aa z(come consentito dalla domanda). Nrappresenta il codice carattere della prossima lettera disponibile nell'ambito corrente e inizia con 97, aka a.

Ecco una versione più leggibile di R(senza la Wmacro):

(defun beta-reduce (S &optional (N 97))
  (if (symbolp s)
      (symbol-value s)
      (if (eq (car s) 'L)
          ;; lambda
          (let ((v (make-symbol (make-string 1 :initial-element (code-char N)))))
            (progv (list (second s) v)(list v v)
              `(L ,v ,(beta-reduce (third s) (1+ n)))))
          (let ((fn (beta-reduce (first s) N))
                (arg (beta-reduce (second s) N)))
            (if (and(consp fn)(eq'L(car fn)))
                (progv (list (second fn)) (list arg)
                  (beta-reduce (third fn) N))
                `(,fn ,arg))))))

Risultati intermedi

Analizza da stringa:

CL-USER> (p (make-string-input-stream "K(K(K(KK)))"))
((L X (L U X)) ((L X (L U X)) ((L X (L U X)) ((L X (L U X)) (L X (L U X))))))

Ridurre:

CL-USER> (r *)
(L #:|a| (L #:|a| (L #:|a| (L #:|a| (L #:|a| (L #:|b| #:|a|))))))

(Vedi la traccia dell'esecuzione)

Pretty-print:

CL-USER> (o *)
"a.a.a.a.a.b.a"

test

Riutilizzo la stessa suite di test della risposta Python:

        Input                    Output              Python output (for comparison)

   1.   KSK                      a.b.c.a(c)(b(c))    a.b.c.a(c)(b(c))              
   2.   SII                      a.a(a)              a.a(a)                        
   3.   S(K(SI))K                a.b.b(a)            a.b.b(a)                      
   4.   S(S(KS)K)I               a.b.a(a(b))         a.b.a(a(b))                   
   5.   S(S(KS)K)(S(S(KS)K)I)    a.b.a(a(a(b)))      a.b.a(a(a(b)))                
   6.   K(K(K(KK)))              a.a.a.a.a.b.a       a.b.c.d.e.f.e 
   7.   SII(SII)                 ERROR               ERROR

L'ottavo esempio di test è troppo grande per la tabella sopra:

8.      SS(SS)(SS)
CL      a.b.a(b)(c.b(c)(a(b)(c)))(a(b.a(b)(c.b(c)(a(b)(c))))(b))      
Python  a.b.a(b)(c.b(c)(a(b)(c)))(a(d.a(d)(e.d(e)(a(d)(e))))(b))
  • MODIFICA Ho aggiornato la mia risposta per avere lo stesso comportamento di raggruppamento della risposta di Aditsu , perché costa meno byte da scrivere.
  • La differenza rimanente può essere visto per prove 6 e 8. Il risultato a.a.a.a.a.b.aè corretto e non usa tanto lettere come la risposta Python, dove binding a a, b, ce dnon si fa riferimento.

Prestazione

Il passaggio in rassegna i 7 test che superano e la raccolta dei risultati è immediato (uscita SBCL):

Evaluation took:
  0.000 seconds of real time
  0.000000 seconds of total run time (0.000000 user, 0.000000 system)
  100.00% CPU
  310,837 processor cycles
  129,792 bytes consed

Fare lo stesso test centinaia di volte porta a ... "Archiviazione locale thread esaurita" su SBCL, a causa di una limitazione nota relativa a variabili speciali. Con CCL, chiamare la stessa suite di test 10000 volte richiede 3,33 secondi.


Questa è una soluzione pulita!
FUZxxl,

@FUZxxl Grazie!
coredump,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.