The Turing machine is not a machine. It is a mathematical model, which was formulated by the English mathematician Alan Turing
in 1936.
2021-05-03-21-09-24.mp4
-
We implement a Turing Machine in Python as a class. We define another class for the
read/write
tape of the Turing Machine. The core of the tape inside the class Tape is adictionary
, which contains theentries
of the tape. This way, we can havenegative indices
. APython list
is not a convenientdata structure
, because Python lists are bounded on one side, i.e.bounded by 0
. -
We define the method
__str__(self)
for theclass Tape
.__str__(self)
is called by the built-instr()
function. Theprint
function uses also thestr
function to calculate the"informal"
string representation of an object, in our case the tape of theTM
. The methodget_tape()
of our class TuringMachine makes use of thestr
representation returned by__str__.
-
With the aid of the method
__getitem__()
, we provide a reading access to the tape via indices. The definition of the method__setitem__()
allows a writing access as well, as we can see e.g. in the statementself.__tape[self.__head_position] = y[1]
of our class TuringMachine implementation. -
The class TuringMachine: We define the method
__str__(self)
, which is called by thestr()
built-in function and by the print statement to compute the "informal" string representation of an object, in our case the string representation of a tape
Formally, Turing machine is a tuple of input alphabet, tape alphabet, set of states, transition function, initial state, accepting state, rejecting state and blank symbol. By default,
initial state (qs)
is denoted by q_s,
accepting state (qy)
is denoted by q_y
or True,
rejecting state (qn)
is denoted by q_n
or False
,
blank symbol (□)
is denoted by []
.
The tape alphabet and the set of states are inferred from the transition function
. It is also assumed that the input alphabet contains all symbols from working alphabet and does not contain blank symbol. Therefore, in most cases it is only required to define the transition function
.
You may create an Algorithm object by passing a dictionary of dictionaries
to the constructor. The keys of the outer dictionary are states, the keys of inner dictionaries are symbols, and the values of inner dictionaries represent the transition function values. For example,
Algorithm({
'q_s': {
'[]': True,
'0': ('#', 'q_1', '->'),
'1': False,
'*': ('q_c', '->'),
},
# etc.
})
specifies that if q_s
is the current state, the machine will either
- change state to
True (accepting state)
, if the current symbol is[] (blank)
, - replace current symbol with
#
, change state toq_1
, and move one cell to theright
, if the current symbol is0
, - change state to
False (rejecting state)
, if the current symbol is1
, or - change state to
q_c
and move one cell to theright
, if the current symbol is*
. - Note that there is no obligation for a transition function value to be a
triple (state, symbol, arrow)
, since the missing elements will be interpreted as remaining the current state, not changing the current symbol, or not moving the tape head, respectively. You may also replace tuples of single element, for instance('->',),
with the element itself, that is'->'
. This trick is especially useful in case of arrows orfinal states
.
By convention, Turing machine states are strings that start with q_
, for example q_find
, q_check
or q_back
, but it is also acceptable to use abbreviations such as q_f
, q_c
or q_b
, respectively, and supply additional information as comments if necessary.
You may use any string as symbol, but it is advisable to use single characters as they enable passing initial sequences as strings
, not tuples
or lists
(eg. 011
instead of ('zero', 'one', 'one'))
. Good choices are 0, 1, a, B, *, #, @
etc. Remember that blank symbol is denoted by []
.
arrows={
'<-': lambda x: x-1,
'->': lambda x: x+1,
}
When developing algorithms, you can use <-
and ->
arrows to move the tape head one cell left or right
, respectively. However, you may customize behaviour of head by providing a dictionary of lambdas that accept current head position index and return new index. By default, the tape is infinite in both directions.
You may execute your algorithm for single input by calling run method on Algorithm object. The following code will instantiate a new Turing machine with given input as initial sequence and also print all configurations one by one until machine terminates or step limit is exceeded.
Algorithm({
'q_s': {
'0': ('1', '->'),
'1': ('0', '->'),
'[]': True,
}
}).run('001011001')
You may also customize this behaviour by passing additional keyword arguments. The machine terminates when it reaches any state from the final_states dictionary keys. If that happens, run method returns dictionary value for reached state. By default, you may use q_y
or True
as accepting state in order for run method to return True
, and q_n
or False
as rejecting state
in order to return False
.
final_states={
True: True,
False: False,
'q_y': True,
'q_n': False,
}
Some algorithms may occasionally fall into an infinite loop
. You may limit maximum step number by adjusting step_limit
. You may also pass None to disable this feature. When step limit is reached, an error
will be raised if raise_on_exceed
is set to True
, otherwise None
will be returned. By default,
step_limit=1_000_000,
raise_on_exceed=True
If you need only final result, you may turn off logging by setting print_configurations keyword argument to False
. By default,
print_configurations=True
You may run the test suite by calling test method on Algorithm object. This method requires two arguments: an iterable of initial sequences (tuple, list, generator etc.)
and a function that returns expected output (True, False, or another value from results_states dictionary)
. Note that you may pass either lambda or name of function/method defined with def keyword
.
Algorithm({
# write your algorithm here
}).test(
generate_words('01', 10),
lambda word: len(word) % 2 == 0
)
You may use generate_words function to generate all words from supplied set of symbols of given maximum length. Note that you may also use itertools.chain in order to merge multiple test suites into a single one.