有限状態マシンの例[終了]


25

有限状態マシンの良い例を探しています。言語は特に重要ではなく、良い例です。

コードの実装は便利です(一般化された擬似コード)が、FSMのさまざまな使用法を収集することも非常に便利です。

例は必ずしもコンピューターベースである必要はありません。たとえば、Mike Dunlaveyの鉄道ネットワークの例は非常に便利です。


12
正規表現は有限状態マシンです。
chrisaycock

5
この質問が「建設的ではない」とマークされている理由がわかりません。最初に閉じたという答えを提示してから約2年が経過していることを考えると、実際には非常に建設的で話題になっていると主張します。
アクア

2
@aqua-それは本当にスタックの問題です。プログラマーは、1つの答えがある質問、非常に具体的な質問に答えることが義務付けられています。しかし、このような価値のある質問は、一般的なスタックサイトの定義に基づいて、「建設的」(IMNSHOの用語の非常に低い定義)とは見なされません。率直に言ってプログラマーが本当に有用であるためには、この特定のルールにあまり熱心ではない順守を採用すべきです。
オコド

1
事実上、実際の問題は、Stackサイトは、率直に言って、よく知られ、協力的で、読みやすい形式の非常に数少ない高品質リソースの1つであるということです。(。おそらく、E・ワードを使用せずに)それは教育の「質問」のためにあるサイト形式の必要性を指し、本当に、スタック上のこの還元主義と思われる
ocodo

3
もっと多くの例があれば素晴らしいので、私はまだこの質問を再開するように人々に懇願します。悲しい事実は、新しい回答が追加されていなかった場合、質問は未解決のままだったということです。
オコド

回答:


28

安全(イベントがトリガー)

  • 米国:複数の状態を「ロック」、1「アンロック」状態
  • トランジション:正しい組み合わせ/キーにより、最初のロック状態からロック解除状態に近づき、最終的にロック解除状態になります。間違った組み合わせ/キーは、最初のロック状態(アイドルとして知られることもある)に戻ります。

信号機(時間トリガー|センサー[イベント]トリガー)

  • 状態:赤、黄、緑(最も単純な例)
  • 遷移:タイマーの後、赤を緑に、緑を黄色に、黄色を赤に変更します。さまざまな(より複雑な)状態の車を検知したときにもトリガーできます。

自動販売機(イベントのトリガー、金庫のバリエーション)

  • 状態:IDLE、5_CENTS、10_CENTS、15_CENTS、20_CENTS、25_CENTSなど、VEND、CHANGE
  • 移行:コイン、請求書の挿入時に状態が変更され、正しい金額(またはそれ以上)でVENDに移行し、次にCHANGEまたはIDLE(自動販売機の倫理に応じて)に移行します

可能であれば+1以上。最後のものを除いてすべてがよさそうだ。IDLE、VEND、およびCHANGEである必要があります。値は条件付きであり、IDLEとそれ自体の間の遷移として表される必要があります。また、アイテムが選択されたことを表す状態が必要です。
エヴァンプレイス

@EvanPlaice:アイテムの選択は、単にIDLEからVENDへの変更をトリガーするイベントではありませんか?販売前に選択を確認する方法を想定していない限り。
ミスコ

これら2つの図の可能性はありますか?
オコド

これらはで与えられた例と同様であるFSM Wikipediaのページにもエレベーターが含まれ、:「簡単な例である自動販売機の硬貨の適切な組み合わせが堆積されるディスペンス製品を、エレベーター、停止のシーケンスが要求された床で決定されますライダー、車が待機しているときに順序を変更する信号機、および適切な順序で組み合わせ番号の入力を必要とする組み合わせロック
icc97

1
@ icc97 FSMの例は豊富であり、日常生活でよく見られます。ちなみに、スタック交換の投稿は、Wikipediaページにサンプル情報が含まれる以前の日付です:)
aqua

14

ボーダーゲートウェイプロトコルの例

BGPは、インターネット上のコアルーティングの決定を裏付けるプロトコルです。特定のノードからのホストの到達可能性を判断するためのテーブルを維持し、インターネットを真に分散化しました。

ネットワークでは、各BGPノードはピアであり、6つの状態IdleConnectActiveOpenSentOpenConfirm、およびEstablishedのいずれかを持つ有限状態マシンを使用します。ネットワーク内の各ピア接続は、これらの状態のいずれかを維持します。

BGPプロトコルは、状態を変更するためにピアに送信されるメッセージを決定します。

BPGステートチャート。

BGPステートチャート

アイドル状態

最初の状態はアイドルです。この状態では、BGPはリソースを初期化し、着信接続の試行を拒否し、ピアへの接続を開始します。

つなぐ

2番目の状態はConnectです。この状態では、ルーターは接続が完了するまで待機し、成功するとOpenSent状態に移行します。失敗した場合、ConnectRetryタイマーをリセットし、有効期限が切れるとアクティブ状態に移行します。

アクティブ

アクティブ状態、ルータはゼロとに戻りに接続リトライタイマをリセットし、接続状態。

OpenSent

OpenSent状態、ルータは、リターンの一方のオープンメッセージを待つを送信します。キープアライブメッセージが交換され、正常に受信されると、ルーターは確立状態になります。

設立

設立状態、ルータは、受信/送信することができます:キープアライブを、更新; ピアとの間の通知メッセージ。

BGPに関する詳細情報はウィキペディアにあります


@tcrosley-それはウィキペディアからですので、実際に信用に値しません。
オコド

1
わかりました、図を含めると+1 。:)
tcrosley

私はチャートのラベルをBGPに修正しようとしましたが、それは私をさせません-十分な文字がありません:)
マイク・ダンレイイ

これを描くにはもっと良い方法が必要です。
ジョブ

1
@ジョブ-少し遅い応答、申し訳ありませんが、私はこれがはるかに難解な例であると考え続けています、Safe、自動販売機などは私が思うにはるかに便利です。
オコド

7

これらは、あらゆる種類のものをモデリングするのに役立ちます。たとえば、(通常の政府)-選挙と呼ばれる---(早期キャンペーン)-議会が解散---(大規模なキャンペーン)-選挙->(投票カウント)に沿った状態で、選挙サイクルをモデル化できます。 )。次に、(投票数)-多数決なし->(連合交渉)-合意に達した->(通常の政府)または(投票数)-多数決->(通常の政府)。私は政治的サブゲームを持つゲームでこのスキームのバリアントを実装しました。

ゲームの他の側面でも使用されています。AIは多くの場合、状態ベースです。メニューとレベル間の移行、および死亡またはレベル完了時の移行は、多くの場合FSMによって適切にモデル化されます。


++良い例。
マイクダンラベイ

1
+1パスがあるため、DFM(確定的有限状態マシン)の良い例です。
エヴァンプレイス

4

jquery-csvプラグインで使用されるCSVパーサー

基本的なチョムスキータイプIII文法パーサーです。

正規表現トークナイザーを使用して、文字ごとにデータを評価します。制御文字に遭遇すると、コードはswitchステートメントに渡され、開始状態に基づいてさらに評価されます。非制御文字はグループ化され、まとめてコピーされるため、必要な文字列コピー操作の回数が減ります。

トークナイザー:

var tokenizer = /("|,|\n|\r|[^",\r\n]+)/;

一致の最初のセットは、制御文字です:値区切り文字( ")値区切り文字(、)およびエントリ区切り文字(改行のすべてのバリエーション)。最後の一致は、非制御文字のグループ化を処理します。

パーサーが満たさなければならない10のルールがあります。

  • ルール#1-行ごとに1つのエントリ、各行は改行で終わる
  • ルール#2-ファイルの最後の末尾の改行は省​​略
  • ルール#3-最初の行にはヘッダーデータが含まれます
  • ルール#4-スペースはデータと見なされ、エントリには末尾のカンマを含めないでください
  • 規則#5-行は二重引用符で区切られている場合と区切られていない場合がある
  • ルール#6-改行、二重引用符、およびコンマを含むフィールドは二重引用符で囲む必要があります
  • ルール#7-フィールドを囲むために二重引用符を使用する場合、フィールド内に現れる二重引用符は、その前に別の二重引用符を付けてエスケープする必要があります
  • 修正#1-引用符で囲まれていないフィールドは、
  • 修正#2-引用されたフィールドは、そうでない場合もあります
  • 修正案3-エントリの最後のフィールドにnull値が含まれている場合と含まれていない場合

注:上位7つのルールは、IETF RFC 4180から直接派生しています。最後の3つは、デフォルトですべての値を区切る(引用する)ことのない最新のスプレッドシートアプリ(Excel、Googleスプレッドシートなど)によって導入されたエッジケースをカバーするために追加されました。RFCの変更を元に戻そうとしましたが、まだ問い合わせに対する回答がありません。

十分に説明したので、図を示します。

CSVパーサー有限状態マシン

州:

  1. エントリおよび/または値の初期状態
  2. 開始引用符が見つかりました
  3. 2番目の引用符が見つかりました
  4. 引用符で囲まれていない値が検出されました

遷移:

  • a。引用符で囲まれた値(1)、引用符で囲まれていない値(3)、null値(0)、nullエントリ(0)、および新しいエントリ(0)の両方をチェックします
  • b。2番目の引用文字をチェックします(2)
  • c。エスケープされた引用符(1)、値の終わり(0)、およびエントリの終わり(0)をチェックします
  • d。値の終わり(0)とエントリの終わり(0)をチェックします

注:実際には状態がありません。エスケープされた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固有のラインスプリッター、単一行パーサー、完全な複数行パーサーです。それらはすべて同様の方法で動作し、改行文字の処理方法が異なります。


1
おっ!非常に素晴らしい貢献。
オコド

@Slomojoありがとう、共有できてうれしいです。私はCSのために学校に行かなかったので、私は自分でこのことを学ばなければなりませんでした。これらのオンラインのような高レベルのCSトピックをカバーする実世界のアプリケーションを見つけることは本当に難しいです。パーサーアルゴリズムを最終的に詳細に文書化して、将来的に私のような他の人に役立つようにする予定です。これは良いスタートです。
エヴァンプレイス

私も自習していますが、30年前に始めたので、CSカリキュラムを取り上げました。当時は非常に低レベルの理論を学ぶ方がはるかに簡単だったと思います。単に気晴らしが少なく、金属の近くで仕事をする機会が増えたからです。実際にはインターネットはなく、私たちは皆洞窟に住んでいました。
オコド

3

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がよく見られます。それは、そうでなければ複雑な状況に対するシンプルなソリューションを提供するからです。


3
これらは、字句解析におけるコンパイラ構築の重要な部分でもあります。
jmq

@jmquigley、答えを追加してください。
オコド

1
いくつかのリンクを含む個別の回答を追加しました。
jmq

3

私は、リフト(エレベーター)について考える/モデリングすることが、有限状態マシンの良い例であることを発見しました。導入の方法はほとんど必要ありませんが、実装するのは簡単なことではありません。

状態は、たとえば、1階、1階など、地面から1階への移動、または3階から1階への移動ですが、現在は3階と2階の間などです。

リフトケージと床自体のボタンの効果は、入力を提供します。その効果は、現在の状態とともに押されるボタンの両方に依存します。

上下を除く各フロアには2つのボタンがあります。1つはリフトの上昇を要求し、もう1つは下降を要求します。


2

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ビットの入力情報に基づいてスイッチを通過することで別の軌道に送信できます。


(回答を編集しました。承認してください。)
ocodo

@Slomojo:それでいい。いいね。
マイクダンラベイ

2

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

これが、分散システムにおける単一の計算ノードの動作であり、リンクベースの通信スキームを設定します。多かれ少なかれ。グラフィック形式では、次のようになります。

ここに画像の説明を入力してください


+1興味深い。DGMMとは何ですか?
エヴァンプレイス

@EvanPlaiceそれは、分散型の最大一致ベースの最小重み付き頂点カバーアルゴリズム(DGMM)です...頭字語はやや省略されていますが、Gの由来は問いません。
オコド

@slomojo「G」は「Generalized」を表します。これが派生するシーケンシャルアルゴリズムは、一般化最大マッチングと呼ばれる手法を使用します。
哲学者

@philosodad私も同じように仮定しましたが、仮定を投稿することは嫌いです。
オコド


0

実際には、ステートマシンは次の目的でよく使用されます。

  • 設計目的(プログラム内のさまざまなアクションのモデリング)
  • 自然言語(文法)パーサー
  • 文字列解析

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)

出典:( 最終)状態マシンの実際

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