57

私は仕事のために何かすることがあります、そして私はあなたの助けが必要です。を実装してFSM - Finite State Machine、charシーケンス(A、B、C、A、Cなど)を識別し、それが受け入れられたかどうかを確認します。

State、、の3つのクラスを実装することを考えていEventますMachinestateクラスはでノードを提示します。これFSMを実装することを考えましたState design pattern。すべてのノードは抽象クラスの状態から拡張され、すべてのクラスはさまざまなタイプのイベントを処理し、新しい状態への遷移を示します。あなたの意見ではそれは良い考えですか?

次に、すべての遷移を保存する方法がわかりません。mapここでも、開始点を保持し、次の状態である種のベクトルを取得する、ある種のを実装することを考えましたが、それが良い考えかどうかはわかりません。

私はそれを実装する方法のいくつかのアイデアを得ることができてうれしいです、あるいは多分あなたは私にいくつかの出発点を与えることができます。

FSMをどのように保存する必要がありますか?つまり、プログラムの開始時にツリーをどのように構築する必要がありますか?私はそれをグーグルで検索し、たくさんの例を見つけましたが、私を助けるものは何もありません。

どうもありがとう。

4

7 に答える 7

49

ステートマシンの心臓部は遷移テーブルです。遷移テーブルは、状態とシンボル(イベントと呼んでいるもの)を新しい状態に変換します。これは、状態の2つのインデックス配列にすぎません。健全性と型安全性のために、状態と記号を列挙として宣言します。配列の境界をチェックするために、常に何らかの方法(言語固有)で「長さ」のメンバーを追加します。FSMを手動でコーディングした場合、空白をいじって行と列の形式でコードをフォーマットします。ステートマシンの他の要素は、初期状態と一連の受け入れ状態です。受け入れ状態のセットの最も直接的な実装は、状態によってインデックス付けされたブール値の配列です。ただし、Javaでは、列挙はクラスであり、引数「accepting」を指定できます。

マシンタイプについては、ジェネリッククラスとして記述できます。2つの型引数を取ります。1つは状態用、もう1つはシンボル用、配列引数は遷移表用、単一の状態は初期値用です。他の唯一の詳細(重要ですが)は、列挙型インデックスを使用して配列を直接宣言するための構文がないため、遷移配列のインデックス付けに適した整数を取得するためにEnum.ordinal()を呼び出す必要があることです(ただし、なれ)。

1つの問題をプリエンプトEnumMapするには、遷移テーブルでは機能しません。必要なキーは、単一の列挙値ではなく、列挙値のペアであるためです。

enum State {
    Initial( false ),
    Final( true ),
    Error( false );
    static public final Integer length = 1 + Error.ordinal();

    final boolean accepting;

    State( boolean accepting ) {
        this.accepting = accepting;
    }
}

enum Symbol {
    A, B, C;
    static public final Integer length = 1 + C.ordinal();
}

State transition[][] = {
    //  A               B               C
    {
        State.Initial,  State.Final,    State.Error
    }, {
        State.Final,    State.Initial,  State.Error
    }
};
于 2012-11-05T01:06:14.797 に答える
12

EasyFSMは、FSMの実装に使用できる動的Javaライブラリです。

同じドキュメントは、 Javaの有限ステートマシンにあります。

また、ライブラリは次の場所からダウンロードできます: Java FSMライブラリ:DynamicEasyFSM

于 2014-04-08T08:19:04.553 に答える
10

有限ステートマシンは、2つの異なる方法で実装できます。

オプション1:

ワークフローが事前定義された有限ステートマシン:すべての状態を事前に知っていて、ステートマシンが将来変更されることなくほぼ修正される場合に推奨されます

  1. アプリケーションで考えられるすべての状態を特定する

  2. アプリケーション内のすべてのイベントを特定します

  3. 状態遷移を引き起こす可能性のある、アプリケーションのすべての条件を特定します

  4. イベントの発生は、状態の遷移を引き起こす可能性があります

  5. 状態と遷移のワークフローを決定することにより、有限状態マシンを構築します。

    たとえば、イベント1が状態1で発生した場合、状態は更新され、マシンの状態はまだ状態1のままである可​​能性があります。

    状態1でイベント2が発生した場合、何らかの条件評価で、システムは状態1から状態2に移行します。

この設計は、状態コンテキストのパターンに基づいています。

有限状態機械のプロトタイプクラスを見てください。

オプション2:

動作ツリー: ステートマシンのワークフローが頻繁に変更される場合に推奨されます。ツリーを壊すことなく、新しい動作を動的に追加できます。

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

基本タスククラスはこれらすべてのタスクのインターフェイスを提供し、リーフタスクは今述べたものであり、親タスクは次に実行するタスクを決定する内部ノードです。

タスクには、実際に必要なことを実行するために必要なロジックのみがあり、タスクが開始されたかどうか、更新する必要があるかどうか、タスクが成功して終了したかどうかなどのすべての決定ロジックがTaskControllerにグループ化されます。クラス、および構成によって追加されます。

デコレータは、別のクラスをラップして追加のロジックを与えることにより、別のクラスを「装飾」するタスクです。

最後に、Blackboardクラスは、すべてのタスクが参照する親AIが所有するクラスです。これは、すべてのリーフタスクのナレッジデータベースとして機能します

詳細については、 JaimeBarrachinaVerdiaによるこの記事をご覧ください。

于 2016-04-01T12:00:51.937 に答える
8

うーん、Flyweightを使用して状態を実装することをお勧めします。目的:多数の小さなオブジェクトのメモリオーバーヘッドを回避します。ステートマシンは非常に大きくなる可能性があります。

http://en.wikipedia.org/wiki/Flyweight_pattern

ノードを実装するためにデザインパターンStateを使用する必要があるかどうかはわかりません。ステートマシンのノードはステートレスです。それらは、現在の入力シンボルを現在の状態からの利用可能な遷移に一致させるだけです。つまり、それらがどのように機能するかを完全に忘れていない限り(これは間違いなく可能です)。

私がそれをコーディングしている場合、私は次のようなことをします:

interface FsmNode {
  public boolean canConsume(Symbol sym);
  public FsmNode consume(Symbol sym);
  // Other methods here to identify the state we are in
}

  List<Symbol> input = getSymbols();
  FsmNode current = getStartState();
  for (final Symbol sym : input) {
    if (!current.canConsume(sym)) {
      throw new RuntimeException("FSM node " + current + " can't consume symbol " + sym);
    }
    current = current.consume(sym);
  }
  System.out.println("FSM consumed all input, end state is " + current);

この場合、フライ級は何をしますか?FsmNodeの下には、おそらく次のようなものがあります。

Map<Integer, Map<Symbol, Integer>> fsm; // A state is an Integer, the transitions are from symbol to state number
FsmState makeState(int stateNum) {
  return new FsmState() {
    public FsmState consume(final Symbol sym) {
      final Map<Symbol, Integer> transitions = fsm.get(stateNum);
      if (transisions == null) {
        throw new RuntimeException("Illegal state number " + stateNum);
      }
      final Integer nextState = transitions.get(sym);  // May be null if no transition
      return nextState;
    }
    public boolean canConsume(final Symbol sym) {
      return consume(sym) != null;
    }
  }
}

これにより、必要に応じてステートオブジェクトが作成されます。これにより、はるかに効率的な基盤となるメカニズムを使用して、実際のステートマシンを格納できます。ここで使用するもの(Map(Integer、Map(Symbol、Integer)))は特に効率的ではありません。

ウィキペディアのページは、Javaの文字列実装の場合のように、いくらか類似したオブジェクトが類似したデータを共有する場合に焦点を当てていることに注意してください。私の意見では、Flyweightは少し一般的であり、寿命の短いオブジェクトのオンデマンド作成をカバーします(より多くのCPUを使用して、より効率的な基盤となるデータ構造を節約します)。

于 2012-11-05T11:14:57.067 に答える
7

簡単で軽量なJavaライブラリEasyFlowを考えてみましょう。彼らのドキュメントから:

EasyFlowを使用すると、次のことができます。

  • 複雑なロジックを実装しますが、コードはシンプルでクリーンに保ちます
  • 非同期呼び出しを簡単かつ優雅に処理する
  • イベント駆動型プログラミングアプローチを使用して同時実行を回避する
  • 再帰を回避することでStackOverflowエラーを回避します
  • 複雑なJavaアプリケーションの設計、プログラミング、およびテストを簡素化する
于 2016-04-25T17:36:42.557 に答える
4

Javaを使用して単純な有限状態マシンの例を設計および実装しました。

IFiniteStateMachine
:有限状態マシン に新しい状態を追加したり、
特定のアクションによって次の状態に移行したりするなど、有限状態マシンを管理するためのパブリックインターフェイス。

interface IFiniteStateMachine {
    void setStartState(IState startState);

    void setEndState(IState endState);

    void addState(IState startState, IState newState, Action action);

    void removeState(String targetStateDesc);

    IState getCurrentState();

    IState getStartState();

    IState getEndState();

    void transit(Action action);
}

IState
: 状態名や接続された状態へのマッピングなどの 状態関連情報を取得するためのパブリックインターフェイス。

interface IState {
    // Returns the mapping for which one action will lead to another state
    Map<String, IState> getAdjacentStates();

    String getStateDesc();

    void addTransit(Action action, IState nextState);

    void removeTransit(String targetStateDesc);
}

アクション:状態の遷移を引き起こすクラス。

public class Action {
    private String mActionName;

    public Action(String actionName) {
        mActionName = actionName;
    }

    String getActionName() {
        return mActionName;
    }

    @Override
    public String toString() {
        return mActionName;
    }

}

StateImpl:IStateの実装。アクション状態のマッピングを維持するために、 HashMapなどのデータ構造を適用しました。

public class StateImpl implements IState {
    private HashMap<String, IState> mMapping = new HashMap<>();
    private String mStateName;

    public StateImpl(String stateName) {
        mStateName = stateName;
    }

    @Override
    public Map<String, IState> getAdjacentStates() {
        return mMapping;
    }

    @Override
    public String getStateDesc() {
        return mStateName;
    }

    @Override
    public void addTransit(Action action, IState state) {
        mMapping.put(action.toString(), state);
    }

    @Override
    public void removeTransit(String targetStateDesc) {
        // get action which directs to target state
        String targetAction = null;
        for (Map.Entry<String, IState> entry : mMapping.entrySet()) {
            IState state = entry.getValue();
            if (state.getStateDesc().equals(targetStateDesc)) {
                targetAction = entry.getKey();
            }
        }
        mMapping.remove(targetAction);
    }

}

FiniteStateMachineImpl:IFiniteStateMachineの実装。ArrayListを使用してすべての状態を保持します。

public class FiniteStateMachineImpl implements IFiniteStateMachine {
    private IState mStartState;
    private IState mEndState;
    private IState mCurrentState;
    private ArrayList<IState> mAllStates = new ArrayList<>();
    private HashMap<String, ArrayList<IState>> mMapForAllStates = new HashMap<>();

    public FiniteStateMachineImpl(){}
    @Override
    public void setStartState(IState startState) {
        mStartState = startState;
        mCurrentState = startState;
        mAllStates.add(startState);
        // todo: might have some value
        mMapForAllStates.put(startState.getStateDesc(), new ArrayList<IState>());
    }

    @Override
    public void setEndState(IState endState) {
        mEndState = endState;
        mAllStates.add(endState);
        mMapForAllStates.put(endState.getStateDesc(), new ArrayList<IState>());
    }

    @Override
    public void addState(IState startState, IState newState, Action action) {
        // validate startState, newState and action

        // update mapping in finite state machine
        mAllStates.add(newState);
        final String startStateDesc = startState.getStateDesc();
        final String newStateDesc = newState.getStateDesc();
        mMapForAllStates.put(newStateDesc, new ArrayList<IState>());
        ArrayList<IState> adjacentStateList = null;
        if (mMapForAllStates.containsKey(startStateDesc)) {
            adjacentStateList = mMapForAllStates.get(startStateDesc);
            adjacentStateList.add(newState);
        } else {
            mAllStates.add(startState);
            adjacentStateList = new ArrayList<>();
            adjacentStateList.add(newState);
        }
        mMapForAllStates.put(startStateDesc, adjacentStateList);

        // update mapping in startState
        for (IState state : mAllStates) {
            boolean isStartState = state.getStateDesc().equals(startState.getStateDesc());
            if (isStartState) {
                startState.addTransit(action, newState);
            }
        }
    }

    @Override
    public void removeState(String targetStateDesc) {
        // validate state
        if (!mMapForAllStates.containsKey(targetStateDesc)) {
            throw new RuntimeException("Don't have state: " + targetStateDesc);
        } else {
            // remove from mapping
            mMapForAllStates.remove(targetStateDesc);
        }

        // update all state
        IState targetState = null;
        for (IState state : mAllStates) {
            if (state.getStateDesc().equals(targetStateDesc)) {
                targetState = state;
            } else {
                state.removeTransit(targetStateDesc);
            }
        }

        mAllStates.remove(targetState);

    }

    @Override
    public IState getCurrentState() {
        return mCurrentState;
    }

    @Override
    public void transit(Action action) {
        if (mCurrentState == null) {
            throw new RuntimeException("Please setup start state");
        }
        Map<String, IState> localMapping = mCurrentState.getAdjacentStates();
        if (localMapping.containsKey(action.toString())) {
            mCurrentState = localMapping.get(action.toString());
        } else {
            throw new RuntimeException("No action start from current state");
        }
    }

    @Override
    public IState getStartState() {
        return mStartState;
    }

    @Override
    public IState getEndState() {
        return mEndState;
    }
}

public class example {

    public static void main(String[] args) {
        System.out.println("Finite state machine!!!");
        IState startState = new StateImpl("start");
        IState endState = new StateImpl("end");
        IFiniteStateMachine fsm = new FiniteStateMachineImpl();
        fsm.setStartState(startState);
        fsm.setEndState(endState);
        IState middle1 = new StateImpl("middle1");
        middle1.addTransit(new Action("path1"), endState);
        fsm.addState(startState, middle1, new Action("path1"));
        System.out.println(fsm.getCurrentState().getStateDesc());
        fsm.transit(new Action(("path1")));
        System.out.println(fsm.getCurrentState().getStateDesc());
        fsm.addState(middle1, endState, new Action("path1-end"));
        fsm.transit(new Action(("path1-end")));
        System.out.println(fsm.getCurrentState().getStateDesc());
        fsm.addState(endState, middle1, new Action("path1-end"));
    }

}

Githubの完全な例

于 2018-08-03T17:15:26.897 に答える
-2

これは、上記のサブクラス化の回答をすべて回避する「if-else」のみを使用したFSMのSUPER SIMPLE実装/例です(Javaでのパターンマッチングのための有限ステートマシンの使用から取得。ここで、彼はで終わる文字列を探しています。 「@」の後に数字が続き、その後に「#」が続く-ここで状態グラフを参照してください):

public static void main(String[] args) {
    String s = "A1@312#";
    String digits = "0123456789";
    int state = 0;
    for (int ind = 0; ind < s.length(); ind++) {
        if (state == 0) {
            if (s.charAt(ind) == '@')
                state = 1;
        } else {
            boolean isNumber = digits.indexOf(s.charAt(ind)) != -1;
            if (state == 1) {
                if (isNumber)
                    state = 2;
                else if (s.charAt(ind) == '@')
                    state = 1;
                else
                    state = 0;
            } else if (state == 2) {
                if (s.charAt(ind) == '#') {
                    state = 3;
                } else if (isNumber) {
                    state = 2;
                } else if (s.charAt(ind) == '@')
                    state = 1;
                else
                    state = 0;
            } else if (state == 3) {
                if (s.charAt(ind) == '@')
                    state = 1;
                else
                    state = 0;
            }
        }
    } //end for loop

    if (state == 3)
        System.out.println("It matches");
    else
        System.out.println("It does not match");
}

PS:質問に直接答えることはありませんが、JavaでFSMを非常に簡単に実装する方法を示しています。

于 2020-06-15T20:55:44.840 に答える