Google ClassroomGoogle Classroom
GeoGebraGeoGebra Classroom

計算言語2(スタック付きオートマトン)

このページはマス旅の一部です。 前回、計算式が受理できるかどうかを判定する有限オートマトン(DFA)を作りました。 今回は、スタックを使って答えの出せるオートマトンを作ろう。
スタックつきオートマトンのことを「プッシュダウン・オートマトン(PDA」と呼ぶ。 スタックに積むことをプッシュといい、 スタックの先頭(最上位)を取り出すことをポップといった。 計算式は前回同様で「a+b」を「ab+」と、後置式でかきましょう。 <スタックの使い方> Σ={0,1,+,x}について、N,M∊[0,1],C,D∊[+,x]とラベルを貼ろう。 1演算記号式"NMC"の計算は ①Nを読んで、Nをスタックに積む。スタック先頭がN。 ②Mを読んで、Mをスタックに積む。スタック先頭がM。 ➂Cを読んで、MをポップするとNがスタックに残る。 このあと3種類に分岐する。 もし、C="x"でM=1か、C="+"でM=0なら、何もスタックに積まない。スタック先頭はN。 もし、C="x"でM=0ならば、スタックに0を積む。スタックには0Nが積んである。スタック先頭は0。 最後の場合、C="+"でM=1なら何も積まず、NをポップしてNを反転してスタックに積む。Nの反転がスタック先頭。 (つまり、0+1=1, 1+0=0という計算をしている。) ④最後のステップは、ポップするとスタック先頭Pを答えにできる。 2演算記号式"NMCLD"の計算はどうなるだろうか。 "NMC"まで済んでいるとしたら、 NMCの最後のステップ④をやらずに、 NMCの最初のステップ①も不要になる。Nの代わりにPとなる。スタック先頭がPになっている。 あとは、②のMのかわりにLを読んで、Lをスタックに積む。スタック先頭がLになる。 ➂はD読んでLをポップする。スタック先頭がPになる。。。。省略。スタック先頭はPか0か反転Pになる。 ④最後のステップは、ポップするとスタック先頭にあった数を答えにできる。 こんな流れになるね。 <推移関数を作ろう> 以上をまとめて状態推移関数を作ろう 状態Q={q1,q2,q3,q4,q5} q1="",q2=N,q3=NM,q4=+1,q5="err" スタック格納文字Γ={0,1} エラーにならない対応表だけ抜き出してみよう。 δ関数3要素⇒2要素のしくみになる。 (現在状態、入力記号、スタック「先頭」)=>(次の状態、プッシュする記号列) プッシュする文字がないときは「ε」を使います。 状態q3、q4での動きが複雑ですが、鍵になりますよ。 状態q1は入力記号をプッシュするから、 (q1 , 0 ,Λ) ⇒(q2 , 0) (q1 , 1 ,Λ) ⇒(q2 , 1) 状態q2も入力記号をプッシュするが、空Λはプッシュできないので判定して終わりだから、 (q2 , 0 ,0 ) ⇒(q3 , 00) (q2 , 0 ,1 ) ⇒(q3 , 01) (q2 , 1 ,0 ) ⇒(q3 , 10) (q2 , 1 ,1 ) ⇒(q3 , 11) (q2 , Λ,N ) ⇒受理される。 状態q3は、入力演算記号とスタック先頭の組み合わせで4通りに分岐して、 (q3 , + ,0 ) ⇒(q2 , ε) +算は、ポップが0なら残りをそのまま。 (q3 , + ,1 ) ⇒(q4 , ε) +算は、ポップが1なら残りをそのままでq4へ (q3 , x ,0 ) ⇒(q2 , 0) ×算は、ポップが0なら0をプッシュ。 (q3 , x ,1 ) ⇒(q2 , ε) ×算は、ポップが1なら残りをそのまま。 状態q4は、q3⇒q2の流れのサブ状態のようなものです。記号残りをポップ(0/1)し、反転(1/0)してプッシュするが読み取り位置は足踏みする。(q3 , + ,1 ) ⇒(q4 , ε)からの足踏みなので、q4でも入力記号は+です。 (q4 , + ,1 ) ⇒(q2 , 0) (q4 , + ,0 ) ⇒(q2 , 1) それ以外の組み合わせはすべてエラー。 (q1,_._),(q2,_,_),(q3,_,_).(q4,_._),(q5,_,_) ⇒q5 課題:以上のM=(Q,Σ、Γ、δ、q1,F)で、F={q2}すると、"01+1x"が受理され答えが1となることを確かめよう。 <手動デバック> [q, s, γ]はマシンMが状態qで記号列s、スタックがγのときのプロセスを書き出そう。 [q1,"0 1 + 1 x",Λ]更新 (q1,0,Λ)=>(q2,0)  プッシュ0 [q2,"1 + 1 x" ,0 ]更新 (q2,1,0 )=>(q3,10)  プッシュ1 [q3,"+ 1 x" ,10]更新 (q3,+,1)=> (q4,0) ポップ1 [q4,"1 x" , 0]更新 (q4,1,0)=> (q2,1)  ポップ0、スタック0を反転してプッシュ1 [q2,"1 x" , 1]更新 (q2,1,1)=> (q3,11) q4と同じ記号状態を入力記号列とする。入力記号1をそのままプッシュ1 [q3,"x" ,11]更新 (q3,x,1)=> (q2,1) ポップが1だから、1が残る。 [q2,Λ, , 1]更新 (q2,Λ,1)=> 受理「1」が答え

PDA(プッシュダウン・オートマトン)

<geogebraでコード化> #プログラムのデザインパターンは前回同様、「作成」で基本設定し、「リセット」で初期化、 「更新」でwhileのように順次プロセスを進めるというものです。 #スタック用の変数がstackStrです。 #更新を押すと、 新状態nextStateを現状態curQにセットします。 状態がq4のときは、読み取り位置posを足踏みしますが、それ以外ではposは1つ進み入力文字列InputStrを読みChaInにセットします。 状態文字qiの数字iの部分を状態位置変数diにセットします。 スタック最上位topSは、スタックstackStrが空ならΛとし、それ以外ではstackStrの先頭1文字を読みます。 残り文字restStrはposがInputStrの長さを超えたらΛを、越えなければ、posから最後までセット。 (状態位置変数di、読み取り文字charIn、スタック先頭topS)の組み合わせに応じて、 次の状態文字をcalcNextで求めます、これはIfをネストさせることで長文コマンドで乗り切りましょう。 nextStateをcalcNextで上書きしましょう。ここまでで、状態表示変数が出そろので、 Processに、 読み取り位置pos :<現状態curQ , 残り文字列reststr , スタックstackStr> ==> 次の状態 ( 読み取り文字) と表示されるようにします。 次は、stackStrも更新しておきましょう。 スタックの2文字をsubstackに格納します。 状態番号diが1なら読み取り文字charIn 状態番号diが2なら,空でなければ、読み取り文字charInをスタックに載せます。 状態番号diが3なら,読み取り文字charInが+ならsubstackのまま、charInがxのときはスタック先頭topSが0なら0をsubstackにのせ、1ならsubstackのまま。 状態番号diが4ならtopSが0ならスタックに1をのせ、1ならスタックに0をのせます。 最後に、入力文字が空で現状態curQがq2のときは受理します。 「作成」ボタン #必要な変数を用意します。クリック時のスクリプトに貼り付けてください。 pos = 0 InputStr = "01+1x" Len = Length(InputStr) Input = Join(Split(InputStr, {""}), {"Λ"}) q1 = "q1" q2 = "q2" q3 = "q3" q4 = "q4" q5 = "q5" curQ = q1 nextState = q1 stackStr = "" substack= "" charIn = "" di = 1 reststr = InputStr result = "" Process = pos + ":<q1, " + reststr + ", Λ> ==> q1" 「リセット」ボタン SetValue(pos, 0) SetValue(curQ, "q1") SetValue(nextState, "q1") SetValue(stackStr, "") SetValue(substack, "") SetValue(charIn, "") SetValue(di, 1) SetValue(reststr, InputStr) SetValue(result, "") SetValue(Process, "0:<q1, " + InputStr + ", Λ> ==> q1") 「更新」ボタン SetValue(curQ, nextState) SetValue(pos, If(curQ == "q4", pos, If(pos < Length(Input), pos + 1, pos))) SetValue(charIn, Element(Input, pos)) di = If(curQ == "q1", 1, If(curQ == "q2", 2, If(curQ == "q3", 3, If(curQ == "q4", 4, 5)))) topS = If(stackStr == "", "Λ", Take(stackStr, 1, 1)) SetValue(reststr, If(pos > Len, "Λ", Take(InputStr, pos, Len))) calcNext = If(di == 1, If(charIn == "0" || charIn == "1", "q2", "q5"), If(di == 2, If(charIn == "0" || charIn == "1", "q3", If(charIn == "Λ", "q2", "q5")), If(di == 3, If(charIn == "+" && topS == "1", "q4", If((charIn == "+" && topS == "0") || (charIn == "x" && topS == "1") || (charIn == "x" && topS == "0"), "q2", "q5")), If(di == 4, If(charIn == "+", "q2", "q5"), "q5")))) SetValue(nextState, calcNext) SetValue(Process, pos + ":<" + curQ + ", " + reststr + ", " + If(stackStr == "", "Λ", stackStr) + "> ==> " + calcNext + "(" + charIn + ")") SetValue(substack,If(Length(stackStr) >= 2, Take(stackStr, 2), "")) SetValue(stackStr, If(di == 1, charIn, If(di == 2 && charIn != "Λ", charIn + stackStr, If(di == 3 && charIn == "+", substack, If(di == 3 && charIn == "x" && topS == "0", "0" + substack, If(di == 3 && charIn == "x" && topS == "1", substack, If(di == 4, If(topS == "0", "1" +substack, "0" + substack ), stackStr))))))) SetValue(result, If(charIn == "Λ" && curQ == "q2", "受理 (ANSWER = " + stackStr + ")", result)) <振り返り> 更新は、SetValueの連発、Ifコマンドのネストしまくりですね。入力は改行を入れませんが、 読むときはIfの前で区切って表示すると、日本語に直して読みやすくなりますよ。