Esempi di macchine a stati finiti [chiuso]


25

Sto cercando buoni esempi di macchine a stati finiti; la lingua non è particolarmente importante, solo buoni esempi.

Le implementazioni di codice sono utili (pseudo-codice generalizzato), ma è anche molto utile per raccogliere i vari usi degli FSM.

Gli esempi non devono necessariamente essere basati su computer, ad esempio l'esempio delle reti ferroviarie di Mike Dunlavey è molto utile.


12
Le espressioni regolari sono macchine a stati finiti.
chrisaycock,

5
Non capisco perché questa domanda sia contrassegnata come 'non costruttiva'. Considerando che sono passati quasi 2 anni da quando ho presentato la prima risposta che è chiusa, direi che in realtà era molto costruttiva e in tema.
Aqua

2
@aqua - è davvero più un problema con stack.Programmers, il suo mandato è rispondere a domande, domande molto specifiche, che hanno una risposta. Tuttavia, domande come queste che sono utili, non sono considerate "costruttive" (una definizione molto scadente del termine, in IMNSHO.) In base alla definizione di siti Stack in generale. Francamente, perché i programmatori possano essere veramente utili, dovrebbe adottare un'adesione meno zelante a questa particolare regola, ma io sono vecchio, stanco e altrimenti impegnato, a mettere lo sforzo necessario nel "cercare di sistemare gli stupidi".
ocodo,

1
In effetti il ​​vero problema è che i siti Stack sono, francamente, una delle pochissime risorse di alta qualità, ben note, collaborative e con un buon formato leggibile. Sembra che questo riduzionismo su Stack, indichi davvero la necessità di un formato del sito che sia per "domande" educative (probabilmente senza usare la parola E.)
ocodo

3
Implorerei ancora le persone di riaprire questa domanda, perché più esempi, sarebbero grandi. Il fatto triste è che se non fossero state aggiunte nuove risposte, la domanda sarebbe rimasta aperta.
ocodo

Risposte:


28

Una cassaforte (evento attivato)

  • Stati : più stati "bloccati", uno stato "sbloccato"
  • Transizioni : combinazioni / tasti corretti ti spostano dagli stati bloccati iniziali agli stati bloccati più vicini allo sbloccato, fino a quando non riesci finalmente a sbloccarlo. Combinazioni / chiavi errate ti riportano nello stato iniziale bloccato (a volte noto come inattivo .

Semaforo (tempo attivato | sensore [evento] attivato)

  • Stati : ROSSO, GIALLO, VERDE (esempio più semplice)
  • Transizioni : dopo un timer, passare da ROSSO a VERDE, da VERDE a GIALLO e da GIALLO a ROSSO. Potrebbe anche essere attivato sul rilevamento di automobili in vari stati (più complicati).

Distributore automatico (evento attivato, una variazione della cassaforte )

  • Stati : IDLE, 5_CENTS, 10_CENTS, 15_CENTS, 20_CENTS, 25_CENTS, ecc., VEND, CHANGE
  • Transizioni : modifica dello stato al momento dell'inserimento di monete, banconote, transizione a VEND al momento dell'acquisto corretto (o più), quindi transizione a CHANGE o IDLE (a seconda dell'etica del tuo distributore automatico)

+1 e altro se potessi. Tutto sembra a posto tranne l'ultimo. Dovrebbe essere, IDLE, VEND e CHANGE. I valori sono condizionali e dovrebbero essere rappresentati come la transizione tra IDLE e se stesso. Vorresti anche uno stato che indica che un elemento è stato selezionato.
Evan Plaice,

@EvanPlaice: La selezione di un elemento non sarebbe semplicemente l'evento che fa scattare una modifica da IDLE a VEND? A meno che non stiate immaginando un metodo per confermare la selezione prima della vendita.
Misko,

Qualche possibilità di un diagramma per questi due?
ocodo

Questi sono gli stessi degli esempi forniti nella pagina Wikipedia di FSM , che include anche gli ascensori: "Esempi semplici sono distributori automatici , che erogano prodotti quando si deposita la giusta combinazione di monete, ascensori , la cui sequenza di fermate è determinata dai piani richiesti dai cavalieri, dai semafori , che cambiano sequenza quando le auto sono in attesa e dai lucchetti a combinazione , che richiedono l'inserimento di numeri combinati nell'ordine corretto. "
icc97,

1
@ icc97 Gli esempi di FSM sono numerosi e comuni nella vita di tutti i giorni. Per inciso, il post di scambio di stack precede l'inclusione delle informazioni di esempio sulla pagina di Wikipedia :)
aqua

14

Esempio di protocollo per gateway gateway

BGP è un protocollo che supporta le decisioni di routing di base su Internet. Mantiene una tabella per determinare la raggiungibilità degli host da un determinato nodo e ha reso Internet veramente decentralizzato.

Nella rete, ogni nodo BGP è un peer e utilizza una macchina a stati finiti, con uno dei sei stati Idle , Connect , Active , OpenSent , OpenConfirm e Established . Ogni connessione peer nella rete mantiene uno di questi stati.

Il protocollo BGP determina i messaggi inviati ai peer per modificarne lo stato.

Statistica BPG.

Statechart BGP

Inattivo

Il primo stato inattivo . In questo stato, BGP inizializza le risorse e rifiuta i tentativi di connessione in entrata e avvia una connessione al peer.

Collegare

Il secondo stato Connect . In questo stato, il router attende il completamento della connessione e passa allo stato OpenSent in caso di successo. In caso di esito negativo, reimposta il timer ConnectRetry e passa allo stato Attivo alla scadenza.

Attivo

Nello stato Attivo , il router reimposta il timer ConnectRetry su zero e torna allo stato Connetti .

OpenSent

Nello stato OpenSent , il router invia un messaggio Open e attende uno in cambio. I messaggi Keepalive vengono scambiati e, una volta ricevuta correttamente, il router viene posto nello stato Stabilito .

Stabilito

Nello stato Stabilito , il router può inviare / ricevere: Keepalive; Aggiornare; e messaggi di notifica da / verso il suo pari.

Maggiori informazioni su BGP sono su Wikipedia


@tcrosley - proviene da Wikipedia, quindi non merita davvero credito.
ocodo,

1
ok, +1 per includere un diagramma. :)
tcrosley,

Ho provato a fissare l'etichetta del grafico su BGP, ma non mi avrebbe permesso - non abbastanza personaggi :)
Mike Dunlavey,

Ci deve essere un modo migliore per disegnare questo.
Lavoro

1
@Job - un po 'di risposta in ritardo, scusa, ma ora continuo a pensare che questo sia un esempio troppo esoterico, la Cassaforte, il Distributore automatico, ecc. Sono molto più utili, penso.
ocodo

7

Sono utili per modellare tutti i tipi di cose. Ad esempio, un ciclo elettorale può essere modellato con stati sulla falsariga di (governo normale) - elezione chiamata -> (campagna elettorale anticipata) - Parlamento sciolto -> (campagna elettorale pesante) - elezione -> (conteggio dei voti ). Quindi (conteggio dei voti) - nessuna maggioranza -> (negoziati di coalizione) - accordo raggiunto -> (governo normale) o (conteggio dei voti) - maggioranza -> (governo normale). Ho implementato una variante di questo schema in un gioco con un sottogioco politico.

Sono utilizzati anche in altri aspetti dei giochi: l'IA è spesso basata sullo stato; le transizioni tra menu e livelli e le transizioni in caso di morte o livello completato sono spesso ben modellate dagli FSM.


++ Bell'esempio.
Mike Dunlavey,

1
+1 Un buon esempio di DFM (Deterministic Finite State Machine) perché i percorsi.
Evan Plaice,

4

Il parser CSV utilizzato nel plug-in jquery-csv

È un parser grammaticale di base di tipo III Chomsky .

Un tokenizer regex viene utilizzato per valutare i dati su base char-by-char. Quando viene rilevato un carattere di controllo, il codice viene passato a un'istruzione switch per un'ulteriore valutazione basata sullo stato iniziale. I caratteri non di controllo sono raggruppati e copiati in massa per ridurre il numero di operazioni di copia di stringhe necessarie.

Il tokenizer:

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

Il primo set di corrispondenze sono i caratteri di controllo: delimitatore di valore (") separatore di valore (,) e separatore di voci (tutte le varianti di newline). L'ultima corrispondenza gestisce il raggruppamento di caratteri non di controllo.

Esistono 10 regole che il parser deve soddisfare:

  • Regola n. 1: una voce per riga, ogni riga termina con una nuova riga
  • Regola n. 2: trascinamento della nuova riga alla fine del file omesso
  • Regola n. 3: la prima riga contiene i dati dell'intestazione
  • Regola n. 4: gli spazi sono considerati dati e le voci non devono contenere una virgola finale
  • Regola n. 5: le righe possono essere o meno delimitate da virgolette doppie
  • Regola n. 6: i campi che contengono interruzioni di riga, virgolette doppie e virgole devono essere racchiusi tra virgolette doppie
  • Regola n. 7 - Se per racchiudere i campi vengono utilizzate le virgolette doppie, è necessario evitare una virgoletta doppia all'interno di un campo precedendola con un'altra virgoletta doppia
  • Emendamento n. 1 - Un campo non quotato può o può
  • Emendamento n. 2 - Un campo tra virgolette può o meno
  • Emendamento n. 3 - L'ultimo campo di una voce può contenere o meno un valore nullo

Nota: le prime 7 regole sono derivate direttamente da IETF RFC 4180 . Gli ultimi 3 sono stati aggiunti ai casi limite introdotti dalle moderne app per fogli di calcolo (ex Excel, Google Spreadsheet) che non delimitano (ovvero citano) tutti i valori per impostazione predefinita. Ho provato a contribuire con le modifiche alla RFC ma non ho ancora ricevuto risposta alla mia richiesta.

Basta con il wind-up, ecco il diagramma:

Macchina a stati finiti parser CSV

Stati:

  1. stato iniziale per una voce e / o un valore
  2. è stata trovata una citazione di apertura
  3. è stata rilevata una seconda citazione
  4. è stato rilevato un valore non quotato

transizioni:

  • un. controlla sia i valori tra virgolette (1), i valori non quotati (3), i valori null (0), le voci null (0) e le nuove voci (0)
  • b. controlla un secondo preventivo char (2)
  • c. verifica la quotazione di escape (1), la fine del valore (0) e la fine della registrazione (0)
  • d. controlla la fine del valore (0) e la fine della voce (0)

Nota: in realtà manca uno stato. Dovrebbe esserci una linea da 'c' -> 'b' contrassegnata con lo stato '1' perché un secondo delimitatore con escape indica che il primo delimitatore è ancora aperto. In effetti, probabilmente sarebbe meglio rappresentarlo come un'altra transizione. La creazione di questi è un'arte, non esiste un solo modo corretto.

Nota: manca anche uno stato di uscita ma su dati validi il parser termina sempre sulla transizione 'a' e nessuno degli stati è possibile perché non è rimasto nulla da analizzare.

La differenza tra stati e transizioni:

Uno stato è finito, nel senso che può essere dedotto solo per significare una cosa.

Una transizione rappresenta il flusso tra gli stati, quindi può significare molte cose.

Fondamentalmente, la relazione stato-> transizione è 1 -> * (ovvero da uno a molti). Lo stato definisce "che cos'è" e la transizione definisce "come viene gestita".

Nota: non preoccuparti se l'applicazione di stati / transizioni non sembra intuitiva, non è intuitiva. Ci è voluto un po 'di corrispondenza con qualcuno molto più intelligente di me prima che finalmente riuscissi a mantenere il concetto.

Lo pseudo-codice:

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

Nota: questo è l'essenza, in pratica c'è molto altro da considerare. Ad esempio, controllo degli errori, valori null, una riga vuota finale (vale a dire che è valida), ecc.

In questo caso, lo stato è la condizione delle cose quando il blocco di corrispondenza regex termina un'iterazione. La transizione è rappresentata come le dichiarazioni del caso.

Come umani, abbiamo la tendenza a semplificare le operazioni di basso livello in abstract di livello superiore, ma lavorare con un FSM sta funzionando con operazioni di basso livello. Mentre gli stati e le transizioni sono molto facili da lavorare individualmente, è intrinsecamente difficile visualizzare il tutto in una volta. Ho trovato più facile seguire ripetutamente i singoli percorsi di esecuzione fino a quando non ho potuto intuire come si svolgono le transizioni. È il re dell'apprendimento della matematica di base, non sarai in grado di valutare il codice da un livello superiore fino a quando i dettagli di basso livello iniziano a diventare automatici.

A parte: se guardi l'implementazione effettiva, mancano molti dettagli. Innanzitutto, tutti i percorsi impossibili genereranno specifiche eccezioni. Dovrebbe essere impossibile colpirli, ma se qualcosa dovesse rompersi scateneranno assolutamente delle eccezioni nel test runner. In secondo luogo, le regole del parser per ciò che è consentito in una stringa di dati CSV "legale" sono piuttosto vaghe, quindi il codice necessario per gestire molti casi limite specifici. Indipendentemente da ciò, questo era il processo utilizzato per deridere l'FSM prima di tutte le correzioni di bug, estensioni e messa a punto.

Come con la maggior parte dei progetti, non è una rappresentazione esatta dell'implementazione ma delinea le parti importanti. In pratica, ci sono in realtà 3 diverse funzioni del parser derivate da questo progetto: uno splitter di linea specifico per CSV, un parser a linea singola e un parser a più righe completo. Operano tutti in modo simile, si differenziano per il modo in cui gestiscono i caratteri newline.


1
Whoa! contributo molto bello.
ocodo,

@Slomojo Grazie, sono felice di condividere. Non sono andato a scuola per CS, quindi ho dovuto imparare queste cose da solo. È davvero difficile trovare applicazioni del mondo reale che trattano argomenti di alto livello di CS come questi online. Sto pianificando di documentare l'algoritmo parser nei minimi dettagli in modo che possa essere utile ad altri come me in futuro. Questo è un buon inizio.
Evan Plaice,

Sono anche autodidatta, ma ho iniziato 30 anni fa, quindi ormai ho coperto il curriculum CS :) Ho pubblicato questa domanda per persone come te e me. Penso che all'epoca fosse molto più semplice imparare la teoria di livello molto basso, semplicemente perché c'era meno distrazione e più opportunità di lavorare vicino al metallo, sebbene non ci fosse davvero internet, e vivevamo tutti in grotte.
ocodo,

3

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

} 

Ecco qua OK, non è geniale, ma mostra l'idea.

Troverai spesso FSM nei prodotti di telecomunicazione perché offrono una soluzione semplice a una situazione altrimenti complessa.


3
Sono anche una parte importante della costruzione del compilatore nell'analisi lessicale.
jmq,

@jmquigley, puoi aggiungere una risposta per favore?
ocodo,

1
Ho aggiunto una risposta separata con un paio di link per te.
jmq

3

Ho scoperto che pensare / modellare un ascensore (ascensore) è un buon esempio di macchina a stati finiti. Richiede poco in termini di introduzione, ma fornisce una situazione tutt'altro che banale da implementare.

Gli stati si trovano, ad esempio, al piano terra, al primo piano, ecc. E spostano il terreno al primo piano, oppure si spostano dal terzo al piano terra, ma attualmente tra il 3 ° e il 2 ° piano e così via.

L'effetto dei pulsanti nella gabbia dell'ascensore e ai piani stessi fornisce input, il cui effetto dipende sia dal pulsante che viene premuto insieme allo stato corrente.

Ogni piano, tranne quello superiore e quello inferiore, avrà due pulsanti: uno per richiedere l'ascensore per salire, l'altro per scendere.


2

OK, ecco un esempio. Supponiamo di voler analizzare un numero intero. Andrebbe qualcosa di simile a dd*dove dè una cifra intera.

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

Ovviamente, come ha detto @Gary, potresti mascherare quelle gotos tramite un'istruzione switch e una variabile di stato. Si noti che può essere strutturato in questo codice, che è isomorfo all'espressione regolare originale:

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

Naturalmente puoi anche farlo con una tabella di ricerca.

Le macchine a stati finiti possono essere realizzate in molti modi e molte cose possono essere descritte come istanze di macchine a stati finiti. Non è una "cosa" tanto quanto un concetto, per pensare alle cose.

Esempio di rete ferroviaria

Un esempio di un FSM è una rete ferroviaria.

Esiste un numero finito di interruttori in cui un treno può andare su uno dei due binari.

Esiste un numero finito di tracce che collegano questi interruttori.

In qualsiasi momento, un treno si trova su un binario, può essere inviato a un altro binario attraversando un interruttore, basato su un singolo bit di informazioni di input.


(Ho apportato una modifica alla tua risposta, spero che approvi.)
Ocodo

@Slomojo: Va bene. Sembra buono.
Mike Dunlavey,

2

Macchina a stati finiti in rubino:

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

Questo è il comportamento di un singolo nodo di calcolo in un sistema distribuito, configurando uno schema di comunicazione basato sul collegamento. Più o meno. In forma grafica è simile al seguente:

inserisci qui la descrizione dell'immagine


+1 interessante. A cosa si riferisce DGMM?
Evan Plaice,

@EvanPlaice è un algoritmo di copertura del vertice ponderato minimo ponderato basato sulla corrispondenza massima (DGMM) ... un acronimo leggermente abbreviato, non chiedermi da dove provenga la G.
ocodo,

@slomojo La "G" sta per "Generalized", l'algoritmo sequenziale da cui questo deriva utilizza una tecnica chiamata matching massimo generalizzato.
Philosodad,

@philosodad Ho assunto altrettanto, ma non mi piace postare ipotesi.
ocodo


0

In pratica, le macchine statali vengono spesso utilizzate per:

  • Scopi di progettazione (modellizzazione delle diverse azioni in un programma)
  • Parser di linguaggio naturale (grammatica)
  • Analisi delle stringhe

Un esempio potrebbe essere una macchina a stati che esegue la scansione di una stringa per vedere se ha la sintassi corretta. I codici postali olandesi, ad esempio, sono formattati come "1234 AB". La prima parte può contenere solo numeri, la seconda solo lettere. È possibile scrivere una macchina a stati che tenga traccia del fatto se si trova nello stato NUMERO o nello stato LETTERA e se riscontra un input errato, rifiutarlo.

Questa macchina a stati accettore ha due stati: numerico e alfa. La macchina a stati si avvia nello stato numerico e inizia a leggere i caratteri della stringa da controllare. Se vengono rilevati caratteri non validi durante uno qualsiasi degli stati, la funzione restituisce un valore False, rifiutando l'input come non valido.

Codice Python:

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)

Fonte: macchine a stati (finiti) in pratica

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.