it-swarm.dev

Beispiele für Finite-State-Maschinen

Ich suche nach guten Beispielen für endliche Zustandsmaschinen. Sprache ist nicht besonders wichtig, nur gute Beispiele.

Code-Implementierungen sind nützlich (verallgemeinerter Pseudocode), aber es ist auch sehr nützlich, die verschiedenen Verwendungen von FSMs zu erfassen.

Beispiele müssen nicht unbedingt computergestützt sein, zum Beispiel ist das Beispiel von Mike Dunlaveys Eisenbahnnetzwerken sehr nützlich.

26
ocodo

A Safe (Ereignis ausgelöst)

  • Zustände : Mehrere "gesperrte" Zustände, ein "entsperrter" Zustand
  • Übergänge : Durch korrekte Kombinationen/Tasten gelangen Sie von den ursprünglichen gesperrten Zuständen in die gesperrten Zustände, die näher an der Entsperrung liegen, bis Sie schließlich die Entsperrung erreichen. Falsche Kombinationen/Schlüssel versetzen Sie wieder in den anfänglichen gesperrten Zustand (manchmal bekannt als Leerlauf .

Ampel (Zeit ausgelöst | Sensor [Ereignis] ausgelöst)

  • Zustände : ROT, GELB, GRÜN (einfachstes Beispiel)
  • Übergänge : Nach einem Timer wechseln Sie ROT zu GRÜN, GRÜN zu GELB und GELB zu ROT. Könnte auch ausgelöst werden, wenn Autos in verschiedenen (komplizierteren) Zuständen erfasst werden.

Verkaufsautomat (Ereignis ausgelöst, eine Variation des sicher)

  • Zustände : IDLE, 5_CENTS, 10_CENTS, 15_CENTS, 20_CENTS, 25_CENTS usw., VEND, CHANGE
  • Übergänge : Statusänderungen beim Einlegen von Münzen, Scheinen, Übergang zu VEND bei korrektem Kaufbetrag (oder mehr), dann Übergang zu CHANGE oder IDLE (abhängig) wie ethisch Ihr Verkaufsautomat ist)
28
aqua

Beispiel für ein Border Gateway-Protokoll

BGP ist ein Protokoll, das die wichtigsten Routing-Entscheidungen im Internet unterstützt. Es verwaltet eine Tabelle, um die Erreichbarkeit von Hosts von einem bestimmten Knoten aus zu bestimmen, und hat das Internet wirklich dezentralisiert.

Im Netzwerk ist jeder BGP Knoten ein Peer und verwendet eine endliche Zustandsmaschine mit einem von sechs Zuständen Leerlauf , Connect, Active, OpenSent, OpenConfirm und Etabliert. Jede Peer-Verbindung im Netzwerk behält einen dieser Zustände bei.

Das BGP-Protokoll bestimmt die Nachrichten, die an Peers gesendet werden, um deren Status zu ändern.

BPG-Zustandsdiagramm.

BGP statechart

Leerlauf

Der erste Zustand Leerlauf . In diesem Zustand initialisiert BGP Ressourcen, lehnt eingehende Verbindungsversuche ab und initiiert eine Verbindung zum Peer.

Verbinden

Der zweite Zustand Verbinden . In diesem Zustand wartet der Router auf den Abschluss der Verbindung und wechselt bei Erfolg in den Zustand OpenSent . Wenn dies nicht erfolgreich ist, wird der ConnectRetry-Timer zurückgesetzt und nach Ablauf in den Status Active übergegangen.

Aktiv

Im Status Active setzt der Router den ConnectRetry-Timer auf Null zurück und kehrt in den Status Connect zurück.

OpenSent

Im Status OpenSent sendet der Router eine Open-Nachricht und wartet auf eine Antwort. Keepalive-Nachrichten werden ausgetauscht und nach erfolgreichem Empfang wird der Router in den Status Established versetzt.

Etabliert

Im Status Established kann der Router senden/empfangen: Keepalive; Aktualisieren; und Benachrichtigungsnachrichten an/von seinem Peer.

Mehr Infos zu BGP gibt es auf Wikipedia

14
ocodo

Sie sind nützlich, um alle möglichen Dinge zu modellieren. Zum Beispiel kann ein Wahlzyklus mit Staaten nach dem Vorbild (normale Regierung) modelliert werden - Wahl genannt -> (früher Wahlkampf) - Parlament aufgelöst -> (schwerer Wahlkampf) - Wahl -> (Stimmenzählung) ). Dann entweder (Stimmenzählung) - keine Mehrheit -> (Koalitionsverhandlungen) - Einigung erzielt -> (normale Regierung) oder (Stimmenzählung) - Mehrheit -> (normale Regierung). Ich habe eine Variante dieses Schemas in einem Spiel mit einem politischen Teilspiel implementiert.

Sie werden auch in anderen Aspekten von Spielen verwendet: KI ist oft staatlich; Übergänge zwischen Menüs und Ebenen sowie Übergänge nach Tod oder abgeschlossener Ebene werden von FSMs häufig gut modelliert.

7
Peter Taylor

Der im Plug-In jquery-csv verwendete CSV-Parser

Es ist ein grundlegender Chomsky Typ III Grammatik Parser.

Ein Regex-Tokenizer wird verwendet, um die Daten char für char auszuwerten. Wenn ein Steuerzeichen gefunden wird, wird der Code zur weiteren Auswertung basierend auf dem Startzustand an eine switch-Anweisung übergeben. Nicht-Steuerzeichen werden massenweise gruppiert und kopiert, um die Anzahl der erforderlichen Zeichenfolgenkopiervorgänge zu verringern.

Der Tokenizer:

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

Der erste Satz von Übereinstimmungen besteht aus den Steuerzeichen: Werttrennzeichen ("), Werttrennzeichen (,) und Eintragstrennzeichen (alle Variationen der Zeilenumbrüche). Die letzte Übereinstimmung behandelt die Nicht-Steuerzeichen-Gruppierung.

Es gibt 10 Regeln, die der Parser erfüllen muss:

  • Regel 1 - Ein Eintrag pro Zeile, jede Zeile endet mit einer neuen Zeile
  • Regel 2 - Nachgestellte Zeilenumbrüche am Ende der Datei wurden weggelassen
  • Regel 3 - Die erste Zeile enthält Kopfdaten
  • Regel 4 - Leerzeichen werden als Daten betrachtet und Einträge sollten kein nachfolgendes Komma enthalten
  • Regel Nr. 5 - Zeilen können durch doppelte Anführungszeichen begrenzt sein oder nicht
  • Regel 6 - Felder mit Zeilenumbrüchen, doppelten Anführungszeichen und Kommas sollten in doppelte Anführungszeichen gesetzt werden
  • Regel Nr. 7 - Wenn zum Einschließen von Feldern doppelte Anführungszeichen verwendet werden, muss ein in einem Feld erscheinendes doppeltes Anführungszeichen maskiert werden, indem ein weiteres doppeltes Anführungszeichen vorangestellt wird
  • Änderung Nr. 1 - Ein nicht zitiertes Feld kann oder kann
  • Änderung Nr. 2 - Ein angegebenes Feld kann oder kann nicht
  • Änderung Nr. 3 - Das letzte Feld in einem Eintrag kann einen Nullwert enthalten oder nicht

Hinweis: Die Top 7 Regeln werden direkt von IETF RFC 418 abgeleitet. Die letzten drei wurden hinzugefügt, um Edge-Fälle abzudecken, die von modernen Tabellenkalkulations-Apps (z. B. Excel, Google Spreadsheet) eingeführt wurden, die standardmäßig nicht alle Werte abgrenzen (dh zitieren). Ich habe versucht, die Änderungen am RFC zurückzugeben, habe aber noch keine Antwort auf meine Anfrage erhalten.

Genug mit dem Aufziehen, hier ist das Diagramm:

CSV parser finite state machine

Zustände:

  1. ausgangszustand für einen Eintrag und/oder einen Wert
  2. es wurde ein Eröffnungszitat gefunden
  3. ein zweites Zitat wurde gefunden
  4. es wurde ein Wert ohne Anführungszeichen gefunden

Übergänge:

  • ein. prüft sowohl auf zitierte Werte (1) als auch auf nicht zitierte Werte (3), Nullwerte (0), Nulleinträge (0) und neue Einträge (0)
  • b. prüft auf ein zweites Anführungszeichen char (2)
  • c. prüft auf ein Escape-Zitat (1), das Ende des Wertes (0) und das Ende des Eintrags (0)
  • d. prüft das Ende des Wertes (0) und das Ende des Eintrags (0)

Hinweis: Es fehlt tatsächlich ein Status. Es sollte eine Zeile von 'c' -> 'b' mit dem Status '1' markiert sein, da ein maskiertes zweites Trennzeichen bedeutet, dass das erste Trennzeichen noch offen ist. In der Tat wäre es wahrscheinlich besser, es als einen anderen Übergang darzustellen. Diese zu erschaffen ist eine Kunst, es gibt keinen einzigen richtigen Weg.

Hinweis: Es fehlt auch ein Exit-Status, aber bei gültigen Daten endet der Parser immer beim Übergang 'a' und keiner der Status ist möglich, da nichts mehr zu analysieren ist.

Der Unterschied zwischen Zuständen und Übergängen:

Ein Zustand ist endlich, was bedeutet, dass nur eines gemeint werden kann.

Ein Übergang stellt den Fluss zwischen Zuständen dar, sodass er viele Dinge bedeuten kann.

Grundsätzlich ist die Zustands-> Übergangsbeziehung 1 -> * (dh eins zu viele). Der Status definiert "was es ist" und der Übergang definiert "wie es behandelt wird".

Hinweis: Machen Sie sich keine Sorgen, wenn sich die Anwendung von Zuständen/Übergängen nicht intuitiv anfühlt, sondern nicht intuitiv. Es dauerte einige umfangreiche Korrespondenz mit jemandem, der viel schlauer war als ich, bis ich endlich das Konzept bekam, um zu bleiben.

Der Pseudocode:

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

Hinweis: Dies ist das Wesentliche. In der Praxis gibt es noch viel mehr zu beachten. Zum Beispiel Fehlerprüfung, Nullwerte, eine nachfolgende Leerzeile (dh welche gültig ist) usw.

In diesem Fall ist der Status die Bedingung der Dinge, wenn der Regex-Übereinstimmungsblock eine Iteration beendet. Der Übergang wird als case-Anweisung dargestellt.

Als Menschen neigen wir dazu, Operationen auf niedriger Ebene in Abstracts auf höherer Ebene zu vereinfachen, arbeiten jedoch mit einem FSM is Arbeiten mit Operationen auf niedriger Ebene. Während Zustände und Übergänge sehr einfach einzeln zu bearbeiten sind, ist es von Natur aus schwierig, das Ganze auf einmal zu visualisieren. Ich fand es am einfachsten, den einzelnen Ausführungspfaden immer wieder zu folgen, bis ich mir vorstellen konnte, wie sich die Übergänge abspielen. Es ist wie ein König, der grundlegende Mathematik lernt. Sie werden nicht in der Lage sein, den Code von einer höheren Ebene aus zu bewerten, bis die Details der unteren Ebene automatisch werden.

Nebenbei: Wenn Sie sich die tatsächliche Implementierung ansehen, fehlen viele Details. Erstens lösen alle unmöglichen Pfade bestimmte Ausnahmen aus. Es sollte unmöglich sein, sie zu treffen, aber wenn etwas kaputt geht, lösen sie im Testläufer absolut Ausnahmen aus. Zweitens sind die Parser-Regeln für das, was in einer "legalen" CSV-Datenzeichenfolge zulässig ist, ziemlich locker, sodass der Code für viele bestimmte Edge-Fälle erforderlich ist. Unabhängig von dieser Tatsache war dies der Prozess, der verwendet wurde, um den FSM vor allen Fehlerkorrekturen, Erweiterungen und Feinabstimmungen zu verspotten.

Wie bei den meisten Designs ist es keine exakte Darstellung der Implementierung, sondern beschreibt die wichtigen Teile. In der Praxis gibt es tatsächlich drei verschiedene Parserfunktionen, die von diesem Entwurf abgeleitet sind: einen CSV-spezifischen Zeilensplitter, einen einzeiligen Parser und einen vollständigen mehrzeiligen Parser. Sie arbeiten alle auf ähnliche Weise und unterscheiden sich in der Art und Weise, wie sie mit Zeilenumbrüchen umgehen.

4
Evan Plaice

Ich habe festgestellt, dass das Nachdenken über/Modellieren eines Aufzugs (Aufzug) ein gutes Beispiel für eine endliche Zustandsmaschine ist. Es erfordert wenig Einführung, bietet aber eine alles andere als triviale Situation für die Implementierung.

Die Zustände befinden sich beispielsweise im Erdgeschoss, im ersten Stock usw. und bewegen sich vom Erdgeschoss in das erste Stockwerk oder vom dritten ins Erdgeschoss, derzeit jedoch zwischen dem 3. und 2. Stock und so weiter.

Die Wirkung von Tasten im Aufzugskäfig und auf den Etagen selbst liefert Eingaben, deren Wirkung sowohl von der Taste abhängt, die zusammen mit dem aktuellen Status gedrückt wird.

Jede Etage mit Ausnahme der oberen und unteren Etage verfügt über zwei Tasten: eine, mit der der Aufzug nach oben und die andere nach unten bewegt werden kann.

3
jan

Einfaches FSM in Java

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

} 

Los geht's. OK, es ist nicht brillant, aber es zeigt die Idee.

FSMs finden Sie häufig in Telekommunikationsprodukten, da sie eine einfache Lösung für eine ansonsten komplexe Situation bieten.

3
Gary Rowe

OK, hier ist ein Beispiel. Angenommen, Sie möchten eine Ganzzahl analysieren. Es würde so etwas wie dd* Gehen, wobei d eine ganzzahlige Ziffer ist.

state0:
    if (!isdigit(*p)) goto error;
    p++;
    goto state1;
state1:
    if (!isdigit(*p)) goto success;
    p++;
    goto state1;

Natürlich können Sie, wie @Gary sagte, diese gotos mithilfe einer switch-Anweisung und einer Statusvariablen verschleiern. Beachten Sie, dass dieser Code strukturiert werden kann, der isomorph zum ursprünglichen regulären Ausdruck ist:

if (isdigit(*p)){
    p++;
    while(isdigit(*p)){
        p++;
    }
    // success
}
else {
    // error
}

Natürlich können Sie dies auch mit einer Nachschlagetabelle tun.

Finite-State-Maschinen können auf viele Arten hergestellt werden, und viele Dinge können als Instanzen von Finite-State-Maschinen beschrieben werden. Es ist weniger ein "Ding" als ein Konzept, um über Dinge nachzudenken.

Beispiel für ein Eisenbahnnetz

Ein Beispiel für eine FSM ist ein Eisenbahnnetz.

Es gibt eine endliche Anzahl von Weichen, an denen ein Zug auf eine von zwei Gleisen fahren kann.

Es gibt eine begrenzte Anzahl von Spuren, die diese Schalter verbinden.

Ein Zug befindet sich jederzeit auf einem Gleis und kann durch Überqueren einer Weiche auf der Grundlage eines einzelnen Eingabeinformationsbits auf ein anderes Gleis gesendet werden.

2
Mike Dunlavey

Finite State Machine in 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

Dies ist das Verhalten eines einzelnen Rechenknotens in einem verteilten System, das ein verbindungsbasiertes Kommunikationsschema einrichtet. Mehr oder weniger. In grafischer Form sieht es ungefähr so ​​aus:

enter image description here

2
philosodad

Unter diesem Link finden Sie einige einfache Beispiele für die lexikalische Analyse (FSM):

http://ironbark.bendigo.latrobe.edu.au/subjects/SS/clect/clect03.html

Sie können auch das "Drachenbuch" für Beispiele lesen (es ist keine leichte Lektüre)

http://en.wikipedia.org/wiki/Compilers:_Principles,_Techniques,_and_Tools

1
jmq

In der Praxis werden Zustandsmaschinen häufig verwendet für:

  • Entwurfszwecke (Modellierung der verschiedenen Aktionen in einem Programm)
  • Parser für natürliche Sprache (Grammatik)
  • String-Analyse

Ein Beispiel wäre eine Zustandsmaschine, die eine Zeichenfolge durchsucht, um festzustellen, ob sie die richtige Syntax hat. Beispielsweise sind niederländische Postleitzahlen als "1234 AB" formatiert. Der erste Teil darf nur Zahlen enthalten, der zweite nur Buchstaben. Es könnte eine Zustandsmaschine geschrieben werden, die verfolgt, ob sie sich im Status NUMBER oder im Status LETTER befindet. Wenn sie auf falsche Eingaben stößt, lehnen Sie sie ab.

Diese Akzeptorzustandsmaschine hat zwei Zustände: numerisch und alpha. Die Zustandsmaschine startet im numerischen Zustand und beginnt mit dem Lesen der Zeichen der zu prüfenden Zeichenfolge. Wenn in einem der Zustände ungültige Zeichen auftreten, gibt die Funktion einen falschen Wert zurück und lehnt die Eingabe als ungültig ab.

Python-Code:

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)

Quelle: (endliche) Zustandsmaschinen in der Praxis

0
theD