What is a state machine?
An extremely precise description of the state machine is that it is a forward graph consisting of a set of nodes and a corresponding set of transfer functions. A state machine "runs" by responding to a series of events. Each event is within the control of the transfer function belonging to the current node, where the scope of the function is a subset of the node. The function returns the "next" (perhaps the same) node. At least one of these nodes must be an end state. When the end state is reached, the state machine stops.
But an abstract mathematical description (as I've just given it) doesn't really explain the situation in which a state machine can be used to solve actual programming problems. Another strategy is to define a state machine as a mandatory programming language, where nodes are also source lines. From a practical point of view, this definition, although accurate, but it is the same as the first description, is an armchair, not practical. (This is not necessarily the case for descriptive, functional, or constraint-based languages such as Haskell, Scheme, or Prolog.) )
Let's try using examples that are better suited to the actual tasks around us. Logically, each rule expression is equivalent to a state machine, and the parser for each regular expression implements the state machine. In fact, most programmers write state machines without really taking this into account.
In the following example, we will look at the true exploratory definition of the state machine. In general, we have a number of different ways to respond to a limited set of events. In some cases, the response depends only on the event itself. In other cases, however, the appropriate action depends on the previous event.
The state machine discussed in this article is a high-level machine designed to demonstrate a programming solution for a class of problems. If it is necessary to discuss programming issues in response to the category of event behavior, then your solution is likely to be an explicit state machine.
Text Processing state machine
A programming problem that most likely calls an explicit state machine involves processing a text file. Processing a text file typically involves reading a unit of information (usually called a character or line) and then performing the appropriate action on the unit that was just read. In some cases, this processing is "stateless" (that is, each of these units contains enough information to correctly determine what action to take). In other cases, even if the text file is not completely stateless, the data has only a limited context (for example, the operation depends on no more information than the line number). However, in other common text processing problems, the input file is very "state". The meaning of each piece of data depends on the string preceding it (perhaps the string following it). Reports, mainframe data entry, readable text, programming source files, and other kinds of text files are stateful. A simple example is a line of code that might appear in a Python source file:
MyObject = SomeClass (this, so, other)
This line indicates that if there are just a few lines around this line, some of the content is different:
"How to use Someclass:myobject = SomeClass" "" "
We should know that we are in a "block reference" state to make sure that this line of code is part of a comment rather than a Python operation.
When not to use a state machine
When you start writing a processor task for any stateful text file, ask yourself what type of input you want to find in the file. Each type of entry is a candidate for a state. There are several types of these. If the numbers are large or uncertain, the state machine may not be the correct solution. (In this case, some database solutions might be more appropriate.) )
Also, consider whether you need to use a state machine. In many cases, it's best to start with a simpler approach. It may be found that even if the text file is stateful, there is an easy way to read it in chunks (where each piece is a type of input value). In fact, in a single state block, it is necessary to implement a state machine only if the transfer between text types requires content-based computation.
The following simple example illustrates the need to use a state machine. Consider the two rules used to divide a column of numbers into blocks. In the first rule, 0 in the list represents a break between blocks. In the second rule, breaks between blocks occur when the sum of elements in a block exceeds 100. Because it uses an accumulator variable to determine whether a threshold is reached, you cannot "immediately" see the boundary of the child list. Therefore, the second rule might be more appropriate for a mechanism similar to a state machine.
An example of a text file that is slightly stateful but is not well-suited for processing by a state machine is the Windows-style. ini file. This file includes a section header, comments, and many assignments. For example:
; Set the ColorScheme and userlevel[colorscheme]background=redforeground=bluetitle=green[userlevel]login=2title=1
Our example has no practical meaning, but it shows some interesting features of the. ini format.
In a sense, the type of each row is determined by its first character (possibly a semicolon, left parenthesis, or letter).
From another point of view, this format is "stateful", because the keyword "title" probably means that if it appears in each section, then there is a separate content.
You can write a text processor program with the ColorScheme state and USERLEVEL state, which still handles the assignment of each state. But it doesn't seem to be the right way to deal with this problem. For example, you can use Python code to create only natural blocks in this text file, such as:
Processing. INI file with the chunked Python code
Import stringtxt = open ( ' Hypothetical.ini '). Read () sects = string.split (txt, ' [') for sect in sects: # does something with sect, like Get it name # (the stuff up to '] ") and read its assignments
Or, if you prefer, you can use a single current_section variable to determine the location:
Processing. INI file to calculate Python code
For line in open ( ' Hypothetical.ini '). ReadLines (): if line[0] = = ' [': Current_ Section = line (1:-2) elif line[0] = = '; ': pass # Ignore comments else : apply_ Value (current_section, line)
When to use a state machine
Now, we've decided that if the text file is "too simple" to use a state machine, let's look at the situation where the state machine needs to be used. A recent article in this column discusses the utility txt2html, which translates "smart ASCII" (including this article) into HTML. Let's recap.
"Smart ASCII" is a text format that uses some interval conventions to differentiate between types of text blocks, such as headers, regular text, quotations, and code samples. While it is easy for readers or authors to analyze the transfer between these text block types, there is no easy way for the computer to split the "smart ASCII" file into chunks of text that make up it. Unlike the. ini file example, text block types can appear in any order. There is no single delimiter in any case to separate blocks (empty lines usually separate blocks of text, but blank lines in code samples do not necessarily end code samples, and text blocks do not need to be separated by blank lines). State machines seem to be a natural solution because you need to reformat each block of text in different ways to produce the correct HTML output.
The general functions of the txt2html reader are as follows:
- Start in the initial state.
- Reads a line of input.
- The row is moved to a new state or is processed in a way that is appropriate for the current state, depending on the input and current state.
This example is about the simplest scenario you will encounter, but it illustrates the following patterns that we have described:
A simple state machine input loop in Python
Global state, blocks, Bl_num, newblock#--Initialize the globalsstate = "HEADER" blocks = [""]bl_num = 0newblock = 1 fo R line in Fhin.readlines (): if state = = "header": # Blank line means new block of HEADER if BL Ankln.match (line): Newblock = 1 elif textln.match (line): Starttext (line) elif Codeln.match (line): Startcode (line ) Else:if Newblock:starthead (line) else:blocks[bl_num] = Blocks[bl_num] + Line elif state = = "TEX T ": # Blank line means new block of text if Blankln.match (line): Newblock = 1 elif headln.match (line): s Tarthead (line) elif Codeln.match (line): Startcode (line) else:if Newblock:starttext (line) Else:block S[bl_num] = Blocks[bl_num] + Line elif state = = "CODE": # blank line does no change state if Blankln. Match (line): blocks[bl_num] = Blocks[bl_num] + line elif Headln.match (line): Starthead (line) elif Textln.match (l INE): Starttext (line)Else:blocks[bl_num] = Blocks[bl_num] + line else:raise valueerror, "unexpected input block state:" +state
You can use txt2html to download the source file from which the code is extracted (see resources). Note: The variable state is declared global, and its value is changed in a function such as Starttext (). Transfer conditions, such as Textln.match (), are regular expression patterns, but they may also be custom functions. In fact, formatting will be performed later in the program. The state machine parses only the text file into a labeled block in the blocks list.
Abstract state Machine Class
It is easy to use Python to implement abstract state machines in forms and functions. This makes the state machine model of the program more prominent than the simple condition block in the previous example (at first glance, the conditions are no different from other conditions). Also, the following classes and their associated handlers are doing well in the isolated state of operations. In many cases, this improves encapsulation and readability.
Files: statemachine.py
From string import Upper class StateMachine : def __init__ (self): Self.handlers = {} self.startstate = None self.endstates = [] def add_state (self, name, handler, End_state =0): name = UPPER (name) Self.handlers[name] = handler if end_state:self.endStates.append (name) def Set_start (self, name): self.startstate = UPPER (name) def run (self, cargo): try : handler = Self.handlers[self.startstate] except : raise "Initializationerror", "must call. set_ Start () before. Run () " if not self.endstates: raise " Initializationerror ", " at Least one State must is a end_state " while 1: (newstate, cargo) = Handler (cargo) if Upper (NewState) in self.endstates: break Else : handler = Self.handlers[upper (newstate)]
The StateMachine class is actually what the abstract state machine needs. Because using Python to transfer function objects is so simple, compared to similar classes in other languages, this class requires very little use of the number of rows.
To really use the StateMachine class, you need to create some handlers for each state you want to use. The handler must conform to the pattern. It loops through the event until it is transferred to another State, at which point the handler should pass a byte group (which includes the new state name and any cargo required by the new state handler) back.
Using cargo as a variable in the StateMachine class encapsulates the data that the state handler needs (the state handler does not have to call its cargo variable). The state handler uses cargo to pass the content required by the next handler, so the new handler can take over the legacy work of the previous handler. Cargo typically includes a file handle, which allows the next handler to read more data after the previous handler has stopped. Cargo can also be a database connection, a complex class instance, or a list with several items.
Now, let's study the test sample. In this example (outlined in the following code example), cargo is just a number that continually transmits feedback to the iteration function. As long as Val is in a range, the next value of Val is always just Math_func (Val). Once the function returns an out-of-range value, the value is passed to another handler, or the state machine exits after invoking an end-state handler that does nothing. The example illustrates one thing: an event does not have to be an input event. It can also be a calculation of events (this is rare). The difference between state handlers is that they only use different tokens when outputting the events they handle. This function is relatively simple and does not necessarily use a state machine. But it is a good illustration of the concept. The code may be easier to understand than the explanation!
Files: statemachine_test.py
From StateMachine import StateMachine def ones_counter (val): print ' ones state: ' While 1:if Val <= 0 or val >= 30:newstate = "Out_of_range"; Break Elif <= val < 30:newstate = "twenties"; Break Elif <= val < 20:newstate = "TENS"; Break Else:print "@%2.1f+"% val, val = Math_func (val) print ">>" return (NewState, Val) def Tens_counter (val): print "Tens state:", while 1:if Val <= 0 or val >= 30:newstate = "Ou T_of_range "; Break Elif 1 <= val < 10:newstate = "ONES"; Break Elif <= val < 30:newstate = "twenties"; Break Else:print "#%2.1f+"% val, val = Math_func (val) print ">>" return (NewState, Val) def Twenties_counter (val): print "Twenties State:", while 1:if Val <= 0 or val >= 30:newstate = "Out_of_range"; Break Elif 1 <= val < 10:newstate= "ONES"; Break Elif <= val < 20:newstate = "TENS"; Break Else:print "*%2.1f+"% val, val = Math_func (val) print ">>" return (NewState, Val) def Math_func (n): From math import sin return abs (sin (n)) *31 if __name__== "__main__": M = Statemachi NE () m.add_state ("ONES", Ones_counter) m.add_state ("TENS", Tens_counter) m.add_state ("twenties", Twenties_counter) m . Add_state ("Out_of_range", None, end_state=1) m.set_start ("ONES") M.run (1)