SKIコンパイラーの最適化


22

SKIの計算は、ラムダ式を使用していないラムダ計算の変種です。代わりに、アプリケーションとコンビネータSK、およびIのみが使用されます。この課題では、あなたの仕事は、SKI用語をβ標準形のラムダ用語に翻訳することです。


入力仕様

入力は、次のテキスト表現のSKI用語です。オプションの末尾の改行を受け取ることを選択できます。入力は、文字で構成されSKI(、及び)で(ABNF形式で)次の文法を満たすsterm開始記号です。

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

出力仕様

出力は、次のテキスト表現の自由変数のないラムダ項です。オプションの末尾の改行を出力することもできます。出力はlterm、開始記号であるABNF形式の次の文法を満たします。

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

制約

入力がβ正規形であると仮定できます。あなたは、β正規形が多くても26の異なる変数を使用すると仮定するかもしれません。入力と出力の両方が79文字で表現できると仮定できます。

サンプル入力

いくつかのサンプル入力を次に示します。出力は、指定された入力の1つの可能な出力です。

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

得点

オクテットで最短のソリューションが勝ちます。一般的な抜け穴は禁止されています。


7
+1これはクールな挑戦だと思うからです。私はその言葉を理解していませんでした。
アレックスA.

2
ああ、ski.aditsu.netでゴルフをする必要があります:)
aditsu

おそらく両方stermを指定しlterm、かっこがない場合は左結合を使用する必要があります。
ピーターテイラー

@PeterTaylorこの方が良いですか?
FUZxxl

いいえ、実際には間違っていると思います。文法の変更に続いて、SKIとして解析しS(KI)ます。
ピーターテイラー

回答:


3

Haskell、232バイト

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]

オンラインでお試しください!

使い方

これは、異なるパーサーのフロントエンドである「型なしラムダ計算のための通訳書く」ための私の答えもあり、ungolfedバージョンのドキュメントでは。

簡単に言えば、Term = T (Char -> String)ラムダ計算のタイプです。これは、他の用語に自分自身を適用するa :: Term -> Term -> Term方法()と、aとして表示する方法を知っていますString(%) :: Term -> Char -> StringChar。用語の関数をを使用して用語に変換できl :: (Term -> Term) -> Termます。また、結果の用語の適用は関数(a (l f) == f)を呼び出すだけなので、用語は表示時に自動的に通常の形式に縮小されます。


9

ルビー、323バイト

私はこのがらくたがうまくいくとは信じられません:

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}

正規表現の置換を使用して生の文字列でβ削減を実行することは、トニー・ザ・ポニーのものです。それにもかかわらず、少なくとも簡単なテストケースでは、その出力は正しいように見えます。

$ 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)))

K(K(K(KK)))正規表現の再帰が遅いため、デバッグ出力で処理しますが、ラップトップでは約7秒かかります。そのα変換の動作を見ることができます!

$ 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))))))))

取得します:ski.rb:4:in `gsub ':' I 'の例で間違った引数タイプnil(予想される
正規表現

今すぐ修正する必要があります!すでにローカルで修正していましたが、投稿を編集するのを忘れていました。
リン

2
わかりました、それはs ........ l ....................... o ........... w、しかし、それはうまくいくように見えます....最終的に:) S(K(SI))Kの結果は正しくないと思います。
-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)'})

注:の後にwhile 1:、3行がタブ文字でインデントされます。

これは基本的にhttp://ski.aditsu.net/の背後にあるコードであり、Pythonに翻訳され、大幅に簡素化され、非常にゴルフされています。

参照:(コードが圧縮されているため、これはおそらくあまり有用ではありません)

V =変数ターム
A =アプリケーションターム
L =ラムダターム
c =変数カウンター
p =変数をターム
rに置き換える=削減
m =最終変数の再番号付け
u =内部変数の再番号付け(重複する用語の場合)
s =ストリング変換
(パラメーターs =自己)
C =文字列変換用の区切り文字
I、K、S:コンビ
ネーターE =解析

例:

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

(これSII(SII)は既約であるため予想される↑ )

大量のバイトを殺すのを手伝ってくれたMaurisとSp3000に感謝します:)


1
クラス内でも有効def m(a,b,c):return fooにできると確信しているのでm=lambda a,b,c:foo、多くのバイトを節約できます。
リン

@Mauris先端に感謝:)
aditsu

a.b.c.a(c)(b(c))有効なラムダ式として読み取れません:何(c)ですか?
コアダンプ

@coredumpそれは不必要なグループ化を伴うオペランドです...そしてあなたは正しいです、それはOPの文法規則と正確に一致しません。それはどれほど重要なのだろうか。お願いします。
-aditsu

@coredump文法が更新されたので、大丈夫です。
-aditsu

3

Common Lisp、560バイト

「最後に、私はの用途を見つけました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))))))

非ゴルフ

;; 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))))

ベータ削減

変数はPROGV、を使用して、新しいCommon Lispシンボルにリダクション中に動的にバインドされMAKE-SYMBOLます。これにより、名前の衝突をうまく回避できます(バインド変数の望ましくないシャドウイングなど)。を使用することもできますがGENSYM、シンボルのユーザーフレンドリな名前が必要です。そのため、記号はato からの文字で名前が付けられますz(質問で許可されているとおり)。N現在のスコープ内で次に使用可能な文字の文字コードを表し、97、別名で始まりaます。

RWマクロなしの)より読みやすいバージョンを次に示します。

(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))))))

中間結果

文字列から解析する:

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))))))

削減:

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

(実行のトレースを参照)

プリティプリント:

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

テスト

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

8番目のテスト例は、上の表には大きすぎます。

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))
  • 編集筆者は、aditsuの回答と同じグループ化動作を行うために回答を更新しました。これは、書き込むバイト数が少ないためです。
  • 残りの差は、結果がテスト6,8のために見ることができるa.a.a.a.a.b.a正しいとバインディングをするPythonの回答だけ文字として使用していないabcおよびd参照されません。

性能

上記の7つの合格したテストをループし、結果を収集するのはすぐです(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

同じテストを数百回実行すると、... 特殊変数に関する既知の制限により、SBCLで「スレッドローカルストレージが使い果たされました」になります。CCLでは、同じテストスイートを10000回呼び出すには3.33秒かかります。


それはきちんとしたソリューションです!
FUZxxl

@FUZxxlありがとう!
コアダンプ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.