Überprüfung der Eingabe mit endlichen Automaten in Java

Validierung der Eingabe mit endlichen Automaten in Java

1. Überblick

Wenn Sie CS studiert haben, haben Sie zweifellos einen Kurs über Compiler oder ähnliches besucht. In diesen Klassen wird das Konzept des endlichen Automaten (auch als endliche Zustandsmaschine bekannt) vermittelt. Auf diese Weise können die Grammatikregeln von Sprachen formalisiert werden.

Sie können mehr über die Themenhere undhere lesen.

Wie kann dieses vergessene Konzept für uns hochrangigen Programmierer hilfreich sein, die sich keine Gedanken über die Erstellung eines neuen Compilers machen müssen?

Nun, es stellt sich heraus, dass das Konzept viele Geschäftsszenarien vereinfachen und uns die Werkzeuge an die Hand geben kann, um über komplexe Logik nachzudenken.

Als schnelles Beispiel können wir Eingaben auch ohne eine externe Bibliothek von Drittanbietern validieren.

2. Der Algorithmus

Kurz gesagt, eine solche Maschine deklariert Zustände und Wege, um von einem Zustand in einen anderen zu gelangen. Wenn Sie einen Stream durchlaufen, können Sie sein Format mit dem folgenden Algorithmus (Pseudocode) überprüfen:

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

Wir sagen, der Automat "akzeptiert" das angegebene Zeichen, wenn ein Pfeil vom aktuellen Status abweicht, auf dem sich das Zeichen befindet. Das Umschalten von Status bedeutet, dass einem Zeiger gefolgt wird und der aktuelle Status durch den Status ersetzt wird, auf den der Pfeil zeigt.

Wenn die Schleife beendet ist, prüfen wir, ob der Automat „anhalten kann“ (der aktuelle Zustand ist doppelt eingekreist) und diese Eingabe erschöpft ist.

3. Ein Beispiel

Schreiben wir einen einfachen Validator für ein JSON-Objekt, um den Algorithmus in Aktion zu sehen. Hier ist der Automat, der ein Objekt akzeptiert:

image

Beachten Sie, dass der Wert einer der folgenden Werte sein kann: string, integer, boolean, null oder ein anderes JSON-Objekt. Der Kürze halber werden in unserem Beispiel nur Zeichenfolgen betrachtet.

3.1. Der Code

Die Implementierung einer Finite-State-Maschine ist recht einfach. Wir haben folgendes:

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

Die Beziehungen zwischen ihnen sind:

  • Die Zustandsmaschine hat einen StromState und teilt uns mit, ob sie anhalten kann oder nicht (ob der Zustand endgültig ist oder nicht).

  • AState enthält eine Liste von Übergängen, denen gefolgt werden kann (ausgehende Pfeile).

  • EinTransition sagt uns, ob das Zeichen akzeptiert wird und gibt uns die nächstenState

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

Beachten Sie, dassFiniteStateMachine implementation is immutable. Dies ist hauptsächlich der Fall, damit eine einzelne Instanz mehrmals verwendet werden kann.

Im Folgenden haben wir die ImplementierungRtState. Die Methodewith(Transition) gibt die Instanz nach dem Hinzufügen des Übergangs aus Gründen der Sprachkompetenz zurück. EinState sagt uns auch, ob es endgültig ist (doppelt eingekreist) oder nicht.

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

Und schließlichRtTransition, das die Übergangsregel überprüft und die nächstenState geben kann:

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
}

Der obige Code isthere. Mit dieser Implementierung sollten Sie in der Lage sein, jede Zustandsmaschine zu erstellen. Der eingangs beschriebene Algorithmus ist so einfach wie:

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

Überprüfen Sie die TestklasseRtFiniteStateMachineTest, um die MethodebuildJsonStateMachine() anzuzeigen. Beachten Sie, dass ein paar Zustände mehr hinzugefügt werden als im obigen Bild, um auch die Anführungszeichen zu erfassen, die die Zeichenfolgen ordnungsgemäß umgeben.

4. Fazit

Endliche Automaten sind großartige Werkzeuge, mit denen Sie strukturierte Daten validieren können.

Sie sind jedoch nicht allgemein bekannt, da sie bei komplexen Eingaben kompliziert werden können (da ein Übergang nur für ein Zeichen verwendet werden kann). Trotzdem sind sie großartig, wenn es darum geht, ein einfaches Regelwerk zu überprüfen.

Wenn Sie mit Finite-State-Maschinen etwas komplizierter arbeiten möchten, sindStatefulJ undsquirrel zwei Bibliotheken, die es wert sind, untersucht zu werden.

Sie können Codebeispieleon GitHub überprüfen.