有限状態マシンの良い例を探しています。言語は特に重要ではなく、良い例です。
コードの実装は便利です(一般化された擬似コード)が、FSMのさまざまな使用法を収集することも非常に便利です。
例は必ずしもコンピューターベースである必要はありません。たとえば、Mike Dunlaveyの鉄道ネットワークの例は非常に便利です。
有限状態マシンの良い例を探しています。言語は特に重要ではなく、良い例です。
コードの実装は便利です(一般化された擬似コード)が、FSMのさまざまな使用法を収集することも非常に便利です。
例は必ずしもコンピューターベースである必要はありません。たとえば、Mike Dunlaveyの鉄道ネットワークの例は非常に便利です。
回答:
安全(イベントがトリガー)
信号機(時間トリガー|センサー[イベント]トリガー)
自動販売機(イベントのトリガー、金庫のバリエーション)
BGPは、インターネット上のコアルーティングの決定を裏付けるプロトコルです。特定のノードからのホストの到達可能性を判断するためのテーブルを維持し、インターネットを真に分散化しました。
ネットワークでは、各BGPノードはピアであり、6つの状態Idle、Connect、Active、OpenSent、OpenConfirm、およびEstablishedのいずれかを持つ有限状態マシンを使用します。ネットワーク内の各ピア接続は、これらの状態のいずれかを維持します。
BGPプロトコルは、状態を変更するためにピアに送信されるメッセージを決定します。
最初の状態はアイドルです。この状態では、BGPはリソースを初期化し、着信接続の試行を拒否し、ピアへの接続を開始します。
2番目の状態はConnectです。この状態では、ルーターは接続が完了するまで待機し、成功するとOpenSent状態に移行します。失敗した場合、ConnectRetryタイマーをリセットし、有効期限が切れるとアクティブ状態に移行します。
でアクティブ状態、ルータはゼロとに戻りに接続リトライタイマをリセットし、接続状態。
OpenSent状態、ルータは、リターンの一方のオープンメッセージを待つを送信します。キープアライブメッセージが交換され、正常に受信されると、ルーターは確立状態になります。
で設立状態、ルータは、受信/送信することができます:キープアライブを、更新; ピアとの間の通知メッセージ。
これらは、あらゆる種類のものをモデリングするのに役立ちます。たとえば、(通常の政府)-選挙と呼ばれる---(早期キャンペーン)-議会が解散---(大規模なキャンペーン)-選挙->(投票カウント)に沿った状態で、選挙サイクルをモデル化できます。 )。次に、(投票数)-多数決なし->(連合交渉)-合意に達した->(通常の政府)または(投票数)-多数決->(通常の政府)。私は政治的サブゲームを持つゲームでこのスキームのバリアントを実装しました。
ゲームの他の側面でも使用されています。AIは多くの場合、状態ベースです。メニューとレベル間の移行、および死亡またはレベル完了時の移行は、多くの場合FSMによって適切にモデル化されます。
jquery-csvプラグインで使用されるCSVパーサー
基本的なチョムスキータイプIII文法パーサーです。
正規表現トークナイザーを使用して、文字ごとにデータを評価します。制御文字に遭遇すると、コードはswitchステートメントに渡され、開始状態に基づいてさらに評価されます。非制御文字はグループ化され、まとめてコピーされるため、必要な文字列コピー操作の回数が減ります。
トークナイザー:
var tokenizer = /("|,|\n|\r|[^",\r\n]+)/;
一致の最初のセットは、制御文字です:値区切り文字( ")値区切り文字(、)およびエントリ区切り文字(改行のすべてのバリエーション)。最後の一致は、非制御文字のグループ化を処理します。
パーサーが満たさなければならない10のルールがあります。
注:上位7つのルールは、IETF RFC 4180から直接派生しています。最後の3つは、デフォルトですべての値を区切る(引用する)ことのない最新のスプレッドシートアプリ(Excel、Googleスプレッドシートなど)によって導入されたエッジケースをカバーするために追加されました。RFCの変更を元に戻そうとしましたが、まだ問い合わせに対する回答がありません。
十分に説明したので、図を示します。
州:
遷移:
注:実際には状態がありません。エスケープされた2番目の区切り文字は、最初の区切り文字がまだ開いていることを意味するため、状態「1」でマークされた「c」から「b」までの行があるはずです。実際、おそらく別の移行として表す方が良いでしょう。これらを作成することは芸術であり、単一の正しい方法はありません。
注:終了状態もありませんが、有効なデータでは、パーサーは常に遷移 'a'で終了し、解析するものが残っていないため、どの状態も不可能です。
状態と遷移の違い:
状態は有限です。つまり、1つのことだけを意味すると推測できます。
遷移は状態間のフローを表すため、多くのことを意味します。
基本的に、state-> transitionの関係は1-> *(つまり1対多)です。状態は「それが何であるか」を定義し、遷移は「それがどのように処理されるか」を定義します。
注:状態/遷移のアプリケーションが直感的でなくても、心配しないでください。直感的ではありません。概念がついに定着する前に、私よりもはるかに賢い誰かに対応するために大規模な対応が必要でした。
擬似コード:
csv = // csv input string
// init all state & data
state = 0
value = ""
entry = []
output = []
endOfValue() {
entry.push(value)
value = ""
}
endOfEntry() {
endOfValue()
output.push(entry)
entry = []
}
tokenizer = /("|,|\n|\r|[^",\r\n]+)/gm
// using the match extension of string.replace. string.exec can also be used in a similar manner
csv.replace(tokenizer, function (match) {
switch(state) {
case 0:
if(opening delimiter)
state = 1
break
if(new-line)
endOfEntry()
state = 0
break
if(un-delimited data)
value += match
state = 3
break
case 1:
if(second delimiter encountered)
state = 2
break
if(non-control char data)
value += match
state = 1
break
case 2:
if(escaped delimiter)
state = 1
break
if(separator)
endOfValue()
state = 0
break
if(newline)
endOfEntry()
state = 0
break
case 3:
if(separator)
endOfValue()
state = 0
break
if(newline)
endOfEntry()
state = 0
break
}
}
注:これは要点であり、実際にはさらに多くの考慮事項があります。たとえば、エラーチェック、null値、末尾の空白行(つまり有効な行)など。
この場合、状態は、正規表現一致ブロックが反復を終了したときの状態です。遷移は、caseステートメントとして表されます。
人間として、私たちは低レベルの操作をより高いレベルの抽象に単純化する傾向がありますが、FSM での作業は低レベルの操作で行われます。状態と遷移は個別に操作するのが非常に簡単ですが、全体を一度に視覚化することは本質的に困難です。トランジションがどのように展開するかを直観できるまで、実行の個々のパスを何度も繰り返した方が簡単だとわかりました。基本的な数学の学習の王様です。低レベルの詳細が自動的になり始めるまで、より高いレベルからコードを評価することはできません。
余談:実際の実装を見ると、多くの詳細が欠落しています。まず、不可能なパスはすべて特定の例外をスローします。それらをヒットすることは不可能であるはずですが、何かが壊れると、テストランナーで例外が絶対にトリガーされます。第二に、「合法的な」CSVデータ文字列で許可されるもののパーサールールはかなり緩いため、多くの特定のエッジケースを処理するために必要なコードです。その事実に関係なく、これはすべてのバグ修正、拡張、および微調整の前にFSMをモックするために使用されたプロセスでした。
ほとんどの設計と同様に、それは実装の正確な表現ではありませんが、重要な部分の概要を説明しています。実際には、この設計から派生した3つの異なるパーサー関数があります。csv固有のラインスプリッター、単一行パーサー、完全な複数行パーサーです。それらはすべて同様の方法で動作し、改行文字の処理方法が異なります。
JavaのシンプルなFSM
int i=0;
while (i<5) {
switch(i) {
case 0:
System.out.println("State 0");
i=1;
break;
case 1:
System.out.println("State 1");
i=6;
break;
default:
System.out.println("Error - should not get here");
break;
}
}
行くぞ OK、それは素晴らしいものではありませんが、アイデアを示しています。
テレコム製品にはFSMがよく見られます。それは、そうでなければ複雑な状況に対するシンプルなソリューションを提供するからです。
OK、ここに例があります。整数を解析するとします。整数の桁dd*
がどこにあるかのようになりd
ます。
state0:
if (!isdigit(*p)) goto error;
p++;
goto state1;
state1:
if (!isdigit(*p)) goto success;
p++;
goto state1;
もちろん、@ Garyが言っgoto
たように、switchステートメントと状態変数を使用して、それらを隠すことができます。このコードに構造化できることに注意してください。これは、元の正規表現と同型です。
if (isdigit(*p)){
p++;
while(isdigit(*p)){
p++;
}
// success
}
else {
// error
}
もちろん、ルックアップテーブルを使用して行うこともできます。
有限状態マシンはさまざまな方法で作成でき、多くのものは有限状態マシンのインスタンスとして説明できます。それは物事について考えるための概念としての「物」ではありません。
FSMの1つの例は、鉄道ネットワークです。
列車が2つの線路の1つに行くことができるスイッチの数には限りがあります。
これらのスイッチを接続するトラックの数には限りがあります。
列車はいつでも1つの軌道上にあり、1ビットの入力情報に基づいてスイッチを通過することで別の軌道に送信できます。
Rubyの有限状態マシン:
module Dec_Acts
def do_next
@now = @next
case @now
when :invite
choose_round_partner
@next = :wait
when :listen
@next = :respond
when :respond
evaluate_invites
@next = :update_in
when :wait
@next = :update_out
when :update_in, :update_out
update_edges
clear_invites
@next = :exchange
when :exchange
update_colors
clear_invites
@next = :choose
when :choose
reset_variables
choose_role
when :done
@next = :done
end
end
end
これが、分散システムにおける単一の計算ノードの動作であり、リンクベースの通信スキームを設定します。多かれ少なかれ。グラフィック形式では、次のようになります。
字句解析(FSM)の簡単な例については、このリンクをご覧ください。
http://ironbark.bendigo.latrobe.edu.au/subjects/SS/clect/clect03.html
また、例については「ドラゴンブック」を参照してください(軽く読むことではありません)
http://en.wikipedia.org/wiki/Compilers:_Principles,_Techniques,_and_Tools
実際には、ステートマシンは次の目的でよく使用されます。
1つの例は、文字列をスキャンして正しい構文があるかどうかを確認するステートマシンです。たとえば、オランダの郵便番号は「1234 AB」としてフォーマットされます。最初の部分には数字のみ、2番目の文字のみを含めることができます。NUMBER状態であるかLETTER状態であるかを追跡し、間違った入力があった場合は拒否するステートマシンを作成できます。
このアクセプターステートマシンには、数値とアルファの2つの状態があります。ステートマシンは数値状態で起動し、チェックする文字列の文字の読み取りを開始します。いずれかの状態で無効な文字が検出されると、関数はFalse値を返し、入力を無効として拒否します。
Pythonコード:
import string
STATE_NUMERIC = 1
STATE_ALPHA = 2
CHAR_SPACE = " "
def validate_zipcode(s):
cur_state = STATE_NUMERIC
for char in s:
if cur_state == STATE_NUMERIC:
if char == CHAR_SPACE:
cur_state = STATE_ALPHA
elif char not in string.digits:
return False
elif cur_state == STATE_ALPHA:
if char not in string.letters:
return False
return True
zipcodes = [
"3900 AB",
"45D6 9A",
]
for zipcode in zipcodes:
print zipcode, validate_zipcode(zipcode)
出典:( 最終)状態マシンの実際