web-dev-qa-db-fra.com

utilise pour les machines d'état

Dans quels domaines de programmation devrais-je utiliser des machines à états? Pourquoi ? Comment pourrais-je en implémenter un?

EDIT: veuillez fournir un exemple pratique, si ce n'est pas trop demander.

65
Geo

Dans quels domaines de programmation devrais-je utiliser une machine d'état?

Utiliser une machine à états pour représenter un objet (réel ou logique) qui peut exister dans un nombre limité de conditions (" états ") et progresse à partir d'un état au suivant selon un ensemble fixe de règles.

Pourquoi devrais-je utiliser une machine d'état?

Une machine à états est souvent un moyen très compact pour représenter un ensemble de règles et conditions complexes et pour traiter diverses entrées. Vous verrez des machines d'état dans les appareils intégrés qui ont une mémoire limitée. Bien implémentée, une machine à états est auto-documentée car chaque état logique représente une condition physique. Une machine à états peut être incorporée dans une petite quantité de code par rapport à son équivalent procédural et fonctionne de manière extrêmement efficace. De plus, les règles qui régissent les changements d'état peuvent souvent être stockées sous forme de données dans une table, fournissant une représentation compacte qui peut être facilement maintenue.

Comment puis-je en implémenter un?

Exemple trivial:

enum states {      // Define the states in the state machine.
  NO_PIZZA,        // Exit state machine.
  COUNT_PEOPLE,    // Ask user for # of people.
  COUNT_SLICES,    // Ask user for # slices.
  SERVE_PIZZA,     // Validate and serve.
  EAT_PIZZA        // Task is complete.
} STATE;

STATE state = COUNT_PEOPLE;
int nPeople, nSlices, nSlicesPerPerson;

// Serve slices of pizza to people, so that each person gets
/// the same number of slices.   
while (state != NO_PIZZA)  {
   switch (state)  {
   case COUNT_PEOPLE:  
       if (promptForPeople(&nPeople))  // If input is valid..
           state = COUNT_SLICES;       // .. go to next state..
       break;                          // .. else remain in this state.
   case COUNT_SLICES:  
       if (promptForSlices(&nSlices))
          state = SERVE_PIZZA;
        break;
   case SERVE_PIZZA:
       if (nSlices % nPeople != 0)    // Can't divide the pizza evenly.
       {                             
           getMorePizzaOrFriends();   // Do something about it.
           state = COUNT_PEOPLE;      // Start over.
       }
       else
       {
           nSlicesPerPerson = nSlices/nPeople;
           state = EAT_PIZZA;
       }
       break;
   case EAT_PIZZA:
       // etc...
       state = NO_PIZZA;  // Exit the state machine.
       break;
   } // switch
} // while

Notes:

  • L'exemple utilise une switch() avec des états explicites case/break pour plus de simplicité. En pratique, un case "passe souvent" à l'état suivant.

  • Pour faciliter la maintenance d'une grande machine à états, le travail effectué dans chaque case peut être encapsulé dans une fonction "travailleur". Obtenez n'importe quelle entrée en haut de la while(), transmettez-la à la fonction de travail et vérifiez la valeur de retour du travailleur pour calculer l'état suivant.

  • Pour des raisons de compacité, l'intégralité de switch() peut être remplacée par un tableau de pointeurs de fonction. Chaque état est incarné par une fonction dont la valeur de retour est un pointeur vers l'état suivant. Avertissement: Cela peut soit simplifier la machine à états, soit la rendre totalement impossible à gérer, alors réfléchissez bien à l'implémentation!

  • Un périphérique intégré peut être implémenté comme une machine d'état qui ne sort que sur une erreur catastrophique, après quoi il effectue une réinitialisation matérielle et rentre dans la machine d'état.

81
Adam Liss

Déjà d'excellentes réponses. Pour une perspective légèrement différente, envisagez de rechercher un texte dans une chaîne plus grande. Quelqu'un a déjà mentionné les expressions régulières et ce n'est vraiment qu'un cas spécial, bien qu'important.

Considérez l'appel de méthode suivant:

very_long_text = "Bereshit bara Elohim et hashamayim ve'et ha'arets." …
Word = "Elohim"
position = find_in_string(very_long_text, Word)

Comment implémenteriez-vous find_in_string? L'approche facile utiliserait une boucle imbriquée, quelque chose comme ceci:

for i in 0 … length(very_long_text) - length(Word):
    found = true
    for j in 0 … length(Word):
        if (very_long_text[i] != Word[j]):
            found = false
            break
    if found: return i
return -1

Mis à part le fait que cela est inefficace, il forme une machine à états! Les états ici sont quelque peu cachés; permettez-moi de réécrire légèrement le code pour les rendre plus visibles:

state = 0
for i in 0 … length(very_long_text) - length(Word):
    if very_long_text[i] == Word[state]:
        state += 1
        if state == length(Word) + 1: return i
    else:
        state = 0
return -1

Les différents états ici représentent directement toutes les différentes positions dans le mot que nous recherchons. Il y a deux transitions pour chaque nœud dans le graphique: si les lettres correspondent, passez à l'état suivant; pour toutes les autres entrées (c'est-à-dire toutes les autres lettres à la position actuelle), revenez à zéro.

Cette légère reformulation a un énorme avantage: elle peut maintenant être modifiée pour donner de meilleures performances en utilisant certaines techniques de base. En fait, chaque algorithme de recherche de chaînes avancé (actualisant les structures de données d'index pour le moment) s'appuie sur cette machine d'état et en améliore certains aspects.

22
Konrad Rudolph

Quelle sorte de tâche?

N'importe quelle tâche, mais d'après ce que j'ai vu, l'analyse de toute sorte est fréquemment implémentée comme une machine d'état.

Pourquoi?

L'analyse d'une grammaire n'est généralement pas une tâche simple. Pendant la phase de conception, il est assez courant qu'un diagramme d'état soit dessiné pour tester l'algorithme d'analyse. Traduire cela en une implémentation de machine d'état est une tâche assez simple.

Comment?

Eh bien, vous n'êtes limité que par votre imagination.

Je l'ai vu faire avec déclarations de casse et boucles .

Je l'ai vu faire avec labels et goto instructions

Je l'ai même vu faire avec des structures de pointeurs de fonctions qui représentent l'état actuel. Lorsque l'état change, un ou plusieurs pointeur de fonction est mis à jour.

Je l'ai vu se faire uniquement en code, où un changement d'état signifie simplement que vous exécutez dans une section de code différente. (pas de variables d'état, et du code redondant si nécessaire. Cela peut être démontré comme un tri très simple, qui n'est utile que pour de très petits ensembles de données.

int a[10] = {some unsorted integers};

not_sorted_state:;
    z = -1;
    while (z < (sizeof(a) / sizeof(a[0]) - 1)
    {
        z = z + 1
        if (a[z] > a[z + 1])
        {
            // ASSERT The array is not in order
            swap(a[z], a[z + 1];        // make the array more sorted
            goto not_sorted_state;      // change state to sort the array
        }
    }
    // ASSERT the array is in order

Il n'y a pas de variables d'état, mais le code lui-même représente l'état

13
EvilTeach

Le modèle de conception State est une manière orientée objet de représenter l'état d'un objet au moyen d'une machine à états finis. Cela aide généralement à réduire la complexité logique de l'implémentation de cet objet (if imbriqués, nombreux indicateurs, etc.)

8

La plupart des workflows peuvent être implémentés comme des machines à états. Par exemple, traiter des demandes ou des ordonnances de congé.

Si vous utilisez .NET, essayez Windows Workflow Foundation. Vous pouvez implémenter un flux de travail de machine d'état assez rapidement avec lui.

6
Maxam

Si vous utilisez C #, chaque fois que vous écrivez un bloc d'itérateur, vous demandez au compilateur de construire une machine d'état pour vous (en gardant une trace de l'endroit où vous vous trouvez dans l'itérateur, etc.).

4
Jon Skeet

Voici un exemple testé et fonctionnel d'une machine d'état. Supposons que vous soyez sur un flux série (port série, données tcp/ip ou fichier sont des exemples typiques). Dans ce cas, je recherche une structure de paquets spécifique qui peut être divisée en trois parties, la synchronisation, la longueur et la charge utile. J'ai trois états, l'un est inactif, en attente de la synchronisation, le second est que nous avons une bonne synchronisation, l'octet suivant devrait être de longueur, et le troisième état est d'accumuler la charge utile.

L'exemple est purement série avec un seul tampon, comme il est écrit ici, il récupérera d'un mauvais octet ou d'un mauvais paquet, en supprimant éventuellement un paquet mais finalement en récupérant, vous pouvez faire d'autres choses comme une fenêtre coulissante pour permettre une récupération immédiate. Ce serait là que vous avez dit qu'un paquet partiel est coupé puis un nouveau paquet complet commence, le code ci-dessous ne le détectera pas et jettera le paquet partiel ainsi que le paquet entier et récupérera le suivant. Une fenêtre coulissante vous y sauverait si vous aviez vraiment besoin de traiter tous les paquets entiers.

J'utilise ce type de machine d'état tout le temps, que ce soit des flux de données série, TCP/IP, des fichiers E/S. Ou peut-être les protocoles TCP/IP eux-mêmes, disons que vous voulez envoyer un e-mail, ouvrir le port, attendre que le serveur envoie une réponse, envoyer HELO, attendre que le serveur envoie un paquet, envoyer un paquet, attendre la réponse, etc. Essentiellement dans ce cas ainsi que dans le cas ci-dessous, vous pouvez être inactif en attendant que le prochain octet/paquet arrive. Pour vous souvenir de ce que vous attendiez, également pour réutiliser le code qui attend quelque chose que vous pouvez utiliser variables d'état. De la même manière que les machines à états sont utilisées en logique (en attendant la prochaine horloge, qu'attendais-je).

Tout comme dans la logique, vous voudrez peut-être faire quelque chose de différent pour chaque état, dans ce cas, si j'ai un bon schéma de synchronisation, je réinitialise le décalage dans mon stockage ainsi que la réinitialisation de l'accumulateur de somme de contrôle. L'état de longueur de paquet illustre un cas où vous souhaiterez peut-être abandonner le chemin de contrôle normal. Pas tous, en fait, de nombreuses machines d'état peuvent sauter ou boucler dans le chemin normal, celle ci-dessous est à peu près linéaire.

J'espère que cela est utile et je souhaite que les machines d'état soient davantage utilisées dans les logiciels.

Les données de test présentent des problèmes intentionnels que la machine d'état récupère. Il y a des données inutiles après le premier bon paquet, un paquet avec une mauvaise somme de contrôle et un paquet avec une longueur non valide. Ma sortie était:

bon paquet: FA0712345678EB Modèle de synchronisation invalide 0x12 Modèle de synchronisation invalide 0x34 Modèle de synchronisation invalide 0x56 Erreur de somme de contrôle 0xBF Longueur de paquet invalide 0 Modèle de synchronisation invalide 0x12 Modèle de synchronisation invalide 0x34 Modèle de synchronisation invalide 0x56 Modèle de synchronisation invalide 0x78 Modèle de synchronisation invalide 0xEB bon paquet458000 Les données

Les deux bons paquets du flux ont été extraits malgré les mauvaises données. Et les mauvaises données ont été détectées et traitées.

 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>

unsigned char testdata[] =
{
    0xFA,0x07,0x12,0x34,0x56,0x78,0xEB,  
    0x12,0x34,0x56,  
    0xFA,0x07,0x12,0x34,0x56,0x78,0xAA,  
    0xFA,0x00,0x12,0x34,0x56,0x78,0xEB,  
    0xFA,0x08,0x12,0x34,0x56,0x78,0x00,0xEA  
};

unsigned int testoff=0;

//packet structure  
// [0] packet header 0xFA  
// [1] bytes in packet (n)  
// [2] payload  
// ... payload  
// [n-1] checksum  
//  

unsigned int state;

unsigned int packlen;  
unsigned int packoff;  
unsigned char packet[256];  
unsigned int checksum;  

int process_packet( unsigned char *data, unsigned int len )  
{  
    unsigned int ra;  

    printf("good packet:");
    for(ra=0;ra<len;ra++) printf("%02X",data[ra]);
    printf("\n");
}  
int getbyte ( unsigned char *d )  
{  
    //check peripheral for a new byte  
    //or serialize a packet or file  

    if(testoff<sizeof(testdata))
    {
        *d=testdata[testoff++];
        return(1);
    }
    else
    {
        printf("no more test data\n");
        exit(0);
    }
    return(0);
}

int main ( void )  
{  
    unsigned char b;

    state=0; //idle

    while(1)
    {
        if(getbyte(&b))
        {
            switch(state)
            {
                case 0: //idle
                    if(b!=0xFA)
                    {
                        printf("Invalid sync pattern 0x%02X\n",b);
                        break;
                    }
                    packoff=0;
                    checksum=b;
                    packet[packoff++]=b;

                    state++;
                    break;
                case 1: //packet length
                    checksum+=b;
                    packet[packoff++]=b;

                    packlen=b;
                    if(packlen<3)
                    {
                        printf("Invalid packet length %u\n",packlen);
                        state=0;
                        break;
                    }

                    state++;
                    break;
                case 2: //payload
                    checksum+=b;
                    packet[packoff++]=b;

                    if(packoff>=packlen)
                    {
                        state=0;
                        checksum=checksum&0xFF;
                        if(checksum)
                        {
                            printf("Checksum error 0x%02X\n",checksum);
                        }
                        else
                        {
                            process_packet(packet,packlen);
                        }
                    }
                    break;
            }
        }

        //do other stuff, handle other devices/interfaces

    }
}
4
old_timer

Les machines d'État sont partout. Les machines à états sont essentielles dans les interfaces de communication où un message doit être analysé lors de sa réception. En outre, il y a eu de nombreuses fois dans le développement de systèmes embarqués que j'ai dû séparer une tâche en plusieurs tâches en raison de contraintes de temps strictes.

2
Nate

Infrastructure d'assurance qualité, destinée à éliminer les écrans ou à exécuter autrement un processus en cours de test. (Ceci est mon domaine d'expérience particulier; j'ai construit un framework de machine à états en Python pour mon dernier employeur avec un support pour pousser l'état actuel sur une pile et utiliser diverses méthodes de sélection de gestionnaire d'état à utiliser) dans tous nos grattoirs d'écran basés sur TTY.) Le modèle conceptuel convient bien, car en passant par une application TTY, il passe par un nombre limité d'états connus et peut être replacé dans les anciens (pensez à utiliser un menu imbriqué). Cela a été publié (avec la permission de cet employeur); utilisez Bazaar pour vérifier http://web.dyfis.net/bzr/isg_state_machine_framework/ si vous voulez voir le code.

Systèmes de ticket, de gestion des processus et de workflow - si votre ticket a un ensemble de règles déterminant son mouvement entre NOUVEAU, TRIAGÉ, EN COURS, BESOINS-QA, ÉCHEC-QA et VÉRIFIÉ (par exemple), vous avez un machine d'état simple.

Construire de petits systèmes embarqués facilement prouvables - la signalisation des feux de circulation est un exemple clé où la liste de tous les états possibles doit être entièrement énumérée et connue.

Les analyseurs et les lexeurs sont fortement basés sur une machine à états, car la façon dont quelque chose en streaming est déterminé est basée sur l'endroit où vous vous trouvez à ce moment-là.

2
Charles Duffy

Je n'ai rien vu ici qui explique la raison pour laquelle je les vois utilisés.

Pour des raisons pratiques, un programmeur doit généralement en ajouter un lorsqu'il est forcé de retourner un thread/exit en plein milieu d'une opération.

Par exemple, si vous avez une demande HTTP multi-états, vous pourriez avoir un code serveur qui ressemble à ceci:

Show form 1
process form 1
show form 2
process form 2

Le fait est que chaque fois que vous affichez un formulaire, vous devez quitter l'intégralité de votre thread sur le serveur (dans la plupart des langues), même si votre code se déroule de manière logique et utilise les mêmes variables.

L'acte de mettre une pause dans le code et de retourner le thread se fait généralement avec une instruction switch et crée ce qu'on appelle une machine à états (version très basique).

À mesure que vous devenez plus complexe, il peut être très difficile de déterminer quels états sont valides. Les gens définissent alors généralement un " State Transition Table " pour décrire toutes les transitions d'état.

J'ai écrit une bibliothèque de machines d'état , le concept principal étant que vous pouvez réellement implémenter directement votre table de transition d'état. C'était un exercice vraiment soigné, je ne sais pas si ça va bien se passer ...

2
Bill K

Expressions régulières sont un autre exemple où les machines à états finis (ou "automates à états finis") entrent en jeu.

Une expression rationnelle compilée est une machine à états finis, et les ensembles de chaînes que les expressions régulières peuvent correspondre sont exactement les langages que les automates à états finis peuvent accepter (appelés "langages réguliers").

2

Une grande partie de la conception matérielle numérique implique la création de machines à états pour spécifier le comportement de vos circuits. Cela revient un peu si vous écrivez du VHDL.

2
Ryan Fox

Un FSM est utilisé partout où vous avez plusieurs états et devez passer à un état différent lors de la stimulation.

(il s'avère que cela englobe la plupart des problèmes, au moins théoriquement)

2
Paul Nathan

J'ai un exemple d'un système actuel sur lequel je travaille. Je suis en train de construire un système de négociation d'actions. Le processus de suivi de l'état d'une commande peut être complexe, mais si vous créez un diagramme d'état pour le cycle de vie d'une commande, il est beaucoup plus simple d'appliquer de nouvelles transactions entrantes à la commande existante. Il y a beaucoup moins de comparaisons nécessaires pour appliquer cette transaction si vous savez d'après son état actuel que la nouvelle transaction ne peut être que l'une des trois choses plutôt que l'une des 20 choses. Cela rend le code beaucoup plus efficace.

1
dviljoen

Les machines à états finis peuvent être utilisées pour l'analyse morphologique dans n'importe quel langage naturel.

Théoriquement, cela signifie que la morphologie et la syntaxe sont réparties entre les niveaux de calcul, l'un étant tout au plus à l'état fini et l'autre étant au plus légèrement sensible au contexte (d'où la nécessité pour d'autres modèles théoriques de tenir compte de Word-to-Word plutôt que relations morphème-morphème).

Cela peut être utile dans le domaine de la traduction automatique et du glossing Word. En apparence, ce sont des fonctionnalités à faible coût à extraire pour des applications d'apprentissage automatique moins triviales en PNL, telles que l'analyse syntaxique ou de dépendance.

Si vous souhaitez en savoir plus, vous pouvez consulter Finite State Morphology par Beesley et Karttunen, et la Xerox Finite State Toolkit qu'ils ont conçue au PARC.

1
Robert Elwell

Un cas d'utilisation typique est celui des feux de circulation.

Sur une note d'implémentation: Java 5 peuvent avoir des méthodes abstraites, ce qui est un excellent moyen d'encapsuler un comportement dépendant de l'état.

0
thSoft

Bonnes réponses. Voici mes 2 cents. Les machines à états finis sont une idée théorique qui peut être implémentée de différentes manières, comme une table ou un interrupteur while (mais ne dites à personne que c'est une façon de dire goto horreurs ). C'est un théorème que tout FSM correspond à une expression régulière, et vice versa. Puisqu'une expression régulière correspond à un programme structuré, vous pouvez parfois simplement écrire un programme structuré pour implémenter votre FSM. Par exemple, un simple analyseur de nombres pourrait être écrit dans le sens de:

/* implement dd*[.d*] */
if (isdigit(*p)){
    while(isdigit(*p)) p++;
    if (*p=='.'){
        p++;
        while(isdigit(*p)) p++;
    }
    /* got it! */
}

Vous avez eu l'idée. Et, s'il y a un moyen qui fonctionne plus vite, je ne sais pas ce que c'est.

0
Mike Dunlavey

Le code piloté par l'état est un bon moyen d'implémenter certains types de logique (les analyseurs en sont un exemple). Cela peut se faire de plusieurs manières, par exemple:

  • État déterminant quel bit de code est réellement exécuté à un moment donné (c'est-à-dire que l'état est implicite dans le morceau de code que vous écrivez). analyseurs de descente récursifs sont un bon exemple de ce type de code.

  • Indiquez ce que vous devez faire dans une condition telle qu'une instruction switch.

  • Machines à états explicites telles que celles générées par des outils de génération d'analyseurs tels que Lex et Yacc .

Tout le code piloté par l'état n'est pas utilisé pour l'analyse. Un générateur de machine d'état général est smc . Il inhale une définition d'une machine d'état (dans sa langue) et il crachera du code pour la machine d'état dans une variété de langues.