web-dev-qa-db-ja.com

FSMの実装方法-Javaの有限状態マシン

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

StateEvent、およびMachineの3つのクラスを実装すると思います。 stateクラスはFSMにノードを提示します。State design patternで実装することを考えました。すべてのノードは抽象クラス状態から拡張され、すべてのクラスは異なるタイプのイベントを処理し、新しい状態に移行します。あなたの意見では良い考えですか?

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

私はそれを実装する方法についていくつかのアイデアを得ることができれば幸いです。

FSMを保存する方法、つまりプログラムの最初にツリーを構築する方法私はそれをグーグルで検索し、多くの例を見つけましたが、何も私を助けません。

どうもありがとう。

48
Ofir A.

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

マシンタイプについては、ジェネリッククラスとして書くことができます。状態用とシンボル用の2つの型引数、遷移テーブル用の配列引数、初期型用の単一の状態を取ります。その他の唯一の詳細は(重要ですが)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
    }
};
40
eh9

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

同じものに関するドキュメントは、次の場所にあります。 Javaの有限状態マシン

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

11
user2968375

うーん、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のString実装の場合と同様に、幾分類似したオブジェクトが類似したデータを共有する場合に焦点を当てています。私の意見では、Flyweightはより一般的であり、寿命の短いオブジェクトのオンデマンド作成をカバーします(より効率的な基礎となるデータ構造を節約するためにより多くのCPUを使用します)。

8
Anders Johansen

簡単で軽量なJavaライブラリ EasyFlow を検討してください。彼らのドキュメントから:

EasyFlowでできること:

  • 複雑なロジックを実装しますが、コードをシンプルかつクリーンに保ちます
  • 非同期呼び出しを簡単かつエレガントに処理する
  • イベント駆動型プログラミングアプローチを使用して同時実行を回避する
  • 再帰を避けることでStackOverflowエラーを回避する
  • 複雑なJavaアプリケーションの設計、プログラミング、テストを簡素化する
7
btiernay

有限状態マシンは、2つの異なる方法で実装できます。

オプション1:

定義済みのワークフローを備えた有限状態マシン:すべての状態を事前に把握しており、状態マシンが将来変更なしでほぼ​​修正される場合に推奨

  1. アプリケーションで可能なすべての状態を特定する

  2. アプリケーション内のすべてのイベントを識別する

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

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

  5. 状態と遷移のworkflowを決定して、有限状態マシンを構築します。

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

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

この設計は、StateおよびContextパターンに基づいています。

Finite State Machine プロトタイプクラスをご覧ください。

オプション2:

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

enter image description here

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

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

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

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

これをご覧ください 記事 byJaime Barrachina Verdia詳細については

6
Ravindra babu

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

Action:状態の遷移を引き起こすクラス。

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などのデータ構造を適用して、Action-Stateマッピングを保持しました。

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

example

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の完全な例

1
shanwu