Finite state machine

From Rosetta Code
Finite state machine is a draft programming task. It is not yet considered ready to be promoted as a complete task, for reasons that should be found in its talk page.

A Finite state machine (FSM) is computational abstraction which maps a finite number of states to other states within the same set, via transitions. An FSM can only be in one state at any given moment. Transitions can either be explicit or implicit; explicit transitions are triggered by an input signal and implicit transitions by the internal state of the system (that is, the current state). Implicit transitions thus represent "automatic" or sequenced states that are generally processed between explicit transitions (although they can also be used to provide an optional path when no valid transition exists for a given input signal).

Example

Consider the model of a simple vending machine. The machine is initially in the "ready" state, which maps to exactly two states in the following way:

ready -> deposit -> waiting
ready -> quit -> exit

The variables in bold-face represent transitions. Any input signal not corresponding to one of those transitions can either trigger an error or be ignored. Otherwise, the current state is updated and the process is repeated. If, for example, a deposit input signal is encountered, the FSM will move to the "waiting" state, which defines these transitions:

waiting -> select -> dispense
waiting -> refund -> refunding

The "dispense" state defines only one transition:

dispense -> remove -> ready

Note, however, that in this example the "refunding" state doesn't actually require input in order to move to the "ready" state, so an implicit transition is defined as such:

refunding -> ready
Task

Implement a finite state machine which handles both explicit and implicit transitions. Then demonstrate an example which models some real-world process.

See also

BASIC[edit]

Sinclair ZX81 BASIC[edit]

Works with 1k of RAM.

There doesn't seem much point, in BASIC, implementing a 'general' FSM that would accept a list of states and transition rules as parameters, because an unstructured BASIC program in essence already is that list.

Within each state, if the transition is implicit we just GOTO the next state. If it is explicit, we loop until the user presses a key corresponding to a valid transition. Invalid inputs are ignored.

The line 100 GOTO 110 is superfluous, because it would go there anyway; but it is worth including it in case we wanted to modify the program later and transition somewhere else out of the dispense state.

Note that the program uses no variables and makes no use of the return stack: all the state is expressed in the (so to speak) state.

 10 PRINT "PRESS D(EPOSIT) OR Q(UIT)"
20 IF INKEY$="D" THEN GOTO 50
30 IF INKEY$="Q" THEN STOP
40 GOTO 20
50 PRINT "PRESS S(ELECT) OR R(EFUND)"
60 IF INKEY$="S" THEN GOTO 90
70 IF INKEY$="R" THEN GOTO 140
80 GOTO 60
90 PRINT "DISPENSED"
100 GOTO 110
110 PRINT "PRESS R(EMOVE)"
120 IF INKEY$="R" THEN GOTO 10
130 GOTO 120
140 PRINT "REFUNDED"
150 GOTO 10
Output:

It will be seen that the user has pressed, in order, D, R, D, S, R, and Q.

PRESS D(EPOSIT) OR Q(UIT)
PRESS S(ELECT) OR R(EFUND)
REFUNDED
PRESS D(EPOSIT) OR Q(UIT)
PRESS S(ELECT) OR R(EFUND)
DISPENSED
PRESS R(EMOVE)
PRESS D(EPOSIT) OR Q(UIT)

C++[edit]

 
#include <map>
 
template <typename State, typename Transition = State>
class finite_state_machine
{
protected:
State
current;
std::map<State, std::map<Transition, State>>
database;
public:
finite_state_machine()
{
set(State());
}
void
set(State const& state)
{
current = state;
}
State
get() const
{
return current;
}
void
clear()
{
database.clear();
}
void
add(State const& state, Transition const& transition, State const& next)
{
database[state][transition] = next;
}
/*
Add a state which is also it's own transition (and thus a link in a chain of sequences)
*/

void
add(State const& state_and_transition, State const& next)
{
add(state_and_transition, state_and_transition, next);
}
bool
process(Transition const& transition)
{
auto const&
transitions = database[current],
found = transitions.find(transition);
if(found == transitions.end())
return false;
auto const&
next = found->second;
set(next);
return true;
}
/*
Process so-called "automatic transitions" (ie: sequencing)
*/

bool
process()
{
return process(get());
}
/*
A set of utility functions that may be helpful for displaying valid transitions to the user, etc...
*/

template <typename PushBackContainer>
bool
get_valid_transitions(State const& state, PushBackContainer& container)
{
container.clear();
auto const&
found = database.find(state);
if(found == database.end())
return false;
auto const&
transitions = found->second;
if(transitions.size() == 0)
return false;
for(auto const& iterator : transitions)
{
auto const&
transition = iterator.first;
container.push_back(transition);
}
return true;
}
template <typename Container>
bool
get_valid_transitions(Container& container)
{
return get_valid_transitions(get(), container);
}
};
 
/*
Example usage: a simple vending machine
*/

 
#include <string>
#include <vector>
#include <iostream>
 
using namespace
std;
void
print(string const& message)
{
cout << message << endl;
}
int
main()
{
finite_state_machine<string>
machine;
machine.add("ready", "quit", "exit");
machine.add("ready", "deposit", "waiting");
machine.add("waiting", "select", "dispense");
machine.add("waiting", "refund", "refunding");
machine.add("dispense", "remove", "ready");
machine.add("refunding", "ready");
machine.set("ready");
for(;;)
{
string
state = machine.get();
if(state == "ready")
print("Please deposit coins.");
else if(state == "waiting")
print("Please select a product.");
else if(state == "dispense")
print("Dispensed...please remove product from tray.");
else if(state == "refunding")
print("Refunding money...");
else if(state == "exit")
break;
else
print("Internal error: unaccounted state '" + state + "'!");
/*
Handle "automatic" transitions
*/

if(machine.process())
continue;
vector<string>
transitions;
machine.get_valid_transitions(transitions);
string
options;
for(auto const& transition : transitions)
{
if(!options.empty())
options += ", ";
options += transition;
}
print("[" + state + "] Input the next transition (" + options + "): ");
string
transition;
cout << " > ";
cin >> transition;
if(!machine.process(transition))
print( "Error: invalid transition!");
}
}
 
Output:
Please deposit coins.
[ready] Enter the next transition (deposit, quit): 
 > deposit
Please select a product.
[waiting] Enter the next transition (refund, select): 
 > refund
Refunding money...
Please deposit coins.
[ready] Enter the next transition (deposit, quit): 
 > deposit
Please select a product.
[waiting] Enter the next transition (refund, select): 
 > select
Dispensed...please remove product from tray.
[dispense] Enter the next transition (remove): 
 > remove
Please deposit coins.
[ready] Enter the next transition (deposit, quit): 
 > quit