Javaの有限オートマトンを使用した入力の検証
1. 概要
CSを勉強したことがあるなら、間違いなくコンパイラーなどについてのコースを受講したことでしょう。これらのクラスでは、有限オートマトン(有限状態機械とも呼ばれます)の概念が教えられます。 これは、言語の文法規則を形式化する方法です。
では、この忘れられた概念は、新しいコンパイラの構築について心配する必要がない私たち、高レベルのプログラマーにとってどのように役立つのでしょうか。
まあ、概念は多くのビジネスシナリオを簡素化し、複雑なロジックを推論するためのツールを提供できることがわかりました。
簡単な例として、外部のサードパーティライブラリなしで入力を検証することもできます。
2. アルゴリズム
一言で言えば、そのようなマシンは、状態と、ある状態から別の状態に移行する方法を宣言します。 ストリームを挿入する場合、次のアルゴリズム(擬似コード)でその形式を検証できます。
for (char c in input) {
if (automaton.accepts(c)) {
automaton.switchState(c);
input.pop(c);
} else {
break;
}
}
if (automaton.canStop() && input.isEmpty()) {
print("Valid");
} else {
print("Invalid");
}
オートマトンは、現在の状態から矢印があり、その上に文字がある場合、指定された文字を「受け入れる」と言います。 状態の切り替えとは、ポインターを追跡し、現在の状態を矢印が指す状態に置き換えることを意味します。
最後に、ループが終了すると、オートマトンが「停止できる」かどうか(現在の状態が二重丸で囲まれている)かどうか、およびその入力が使い果たされたかどうかを確認します。
3. 例
JSONオブジェクトの簡単なバリデーターを作成して、アルゴリズムの動作を確認しましょう。 オブジェクトを受け入れるオートマトンは次のとおりです。
値は、string、integer、boolean、null、または別のJSONオブジェクトのいずれかです。 簡潔にするために、この例では文字列のみを考慮します。
3.1. コード
有限状態マシンの実装は非常に簡単です。 次のものがあります。
public interface FiniteStateMachine {
FiniteStateMachine switchState(CharSequence c);
boolean canStop();
}
interface State {
State with(Transition tr);
State transit(CharSequence c);
boolean isFinal();
}
interface Transition {
boolean isPossible(CharSequence c);
State state();
}
それらの間の関係は次のとおりです。
-
ステートマシンには現在のStateが1つあり、停止できるかどうか(状態が最終かどうか)を通知します。
-
Stateには、たどることができる遷移のリストがあります(外向きの矢印)
-
Transitionは、文字が受け入れられたかどうかを示し、次のStateを示します。
publi class RtFiniteStateMachine implements FiniteStateMachine {
private State current;
public RtFiniteStateMachine(State initial) {
this.current = initial;
}
public FiniteStateMachine switchState(CharSequence c) {
return new RtFiniteStateMachine(this.current.transit(c));
}
public boolean canStop() {
return this.current.isFinal();
}
}
FiniteStateMachine implementation is immutableに注意してください。 これは主に、単一のインスタンスを複数回使用できるようにするためです。
以下に、実装RtStateがあります。 with(Transition)メソッドは、流暢さのために、遷移が追加された後にインスタンスを返します。 Stateは、それが最終(二重丸)であるかどうかも示します。
public class RtState implements State {
private List transitions;
private boolean isFinal;
public RtState() {
this(false);
}
public RtState(boolean isFinal) {
this.transitions = new ArrayList<>();
this.isFinal = isFinal;
}
public State transit(CharSequence c) {
return transitions
.stream()
.filter(t -> t.isPossible(c))
.map(Transition::state)
.findAny()
.orElseThrow(() -> new IllegalArgumentException("Input not accepted: " + c));
}
public boolean isFinal() {
return this.isFinal;
}
@Override
public State with(Transition tr) {
this.transitions.add(tr);
return this;
}
}
そして最後に、遷移ルールをチェックして次のStateを与えることができるRtTransition:
public class RtTransition implements Transition {
private String rule;
private State next;
public State state() {
return this.next;
}
public boolean isPossible(CharSequence c) {
return this.rule.equalsIgnoreCase(String.valueOf(c));
}
// standard constructors
}
上記のコードはhereです。 この実装により、任意のステートマシンを構築できるはずです。 最初に説明したアルゴリズムは次のように簡単です。
String json = "{\"key\":\"value\"}";
FiniteStateMachine machine = this.buildJsonStateMachine();
for (int i = 0; i < json.length(); i++) {
machine = machine.switchState(String.valueOf(json.charAt(i)));
}
assertTrue(machine.canStop());
テストクラスRtFiniteStateMachineTestをチェックして、buildJsonStateMachine()メソッドを確認します。 文字列を適切に囲む引用符もキャッチするために、上記の画像よりもいくつかの状態が追加されることに注意してください。