prog3_turing.py
.
delta()
that takes a list of transitions lst
, a pair q,a
, and returns the triple (q',b,m)
when (q,a,q',b,m)
is in the list lst
. A move instruction is encoded by the strings "Left"
, "Right"
, or "Stay"
. We assume that the list lst
does not contain several 5-tuples starting with q
and a
because the Turing machine is deterministic. Complete the following skeleton code for delta()
and notice that the function is recursive, it returns the empty list when no transition is found, and a list of three elements otherwise:
def delta(lst,q,a):
if lst == []:
return []
else:
...
if ... :
return [q', b, m]
else:
return delta(lst[1:],q,a)
A Turing machine is encoded using a class:
class TM:
def __init__(self,transitions,qstart,qaccept, qreject):
self.transitions = transitions
self.qstart = qstart
self.qaccept = qaccept
self.qreject = qreject
Next, to encode the tape with the head, let us use the mathematical specification we used in class, that is a tape with its head is an element of (Σ * x Σ x Σ *). This will prove to be efficient when dealing with nondeterminisitic Turing machines in the next section.
class TapeHead:
def __init__(self, left = [], head = '_', right = []):
self.left = left
self.head = head
self.right = right
The three types of operations that can be performed on a tape by a Turing machine is replace the symbol pointed by the head with another symbol, and moving the head left or right.
For example, the tape (abc, d, efg) is encoded as TapeHead(['a','b','c'], 'd', ['e','f','g'])
. Moving the head to the right would produce a TapeHead(['a','b','c','d'], 'e', ['f','g'])
. Moving the head to the left would produce a TapeHead(['a','b'], 'c', ['d','e','f','g'])
. Do not forget the special cases when the head is at the leftmost position of the tape or when the head is at the rightmost position of the tape.
TapeHead
is a persistent data structure, which means that the operations should not modify in place each field but instead create a new TapeHead
object with modified fields. In practice, to make it persitent, you simply have to use the following basic operations on strings: concatenation +
, selecting the head of a list with lst[0]
, get the tail of a list lst[1:]
, and get a list without its last element lst[:-1]
.
Implement functions moveLeft()
, moveRight()
and replace()
that take as input a tape with its head, and returns the new tape with head according to the operation.
def moveLeft(tapehead):
...
return TapeHead(u,a,v)
def moveRight(tapehead):
...
return TapeHead(u,a,v)
def replace(tapehead,b):
...
return TapeHead(u,a,v)
init()
that returns the initial configuration for an input Turing machine and a given input string w
.
def init(tm,w):
...
onestep()
that simulates a single step of computation of a Turing machine given a configuration. The function onestep()
must return the next configuration.
def onestep(tm,q,tapehead):
...
onestep()
, write a function called allsteps()
that computes iteratively all the steps until it reaches an halting state, that is the accept state or the reject state. This function should be recursive and return a halting configuration if the machine halts, or loop forever if that is what the machine is intended to do.
def allsteps(tm,q,tapehead):
...
simulation()
that takes as input a Turing machine and an input string and executes the machine on the input string. The function simulation()
uses init()
and allsteps
and returns a boolean indicating if the input string is accepted or rejected, or it loops forever.
def simulation(tm,w):
...
TM
and TapeHead
.
Extends the functions defined previously to be able to deal with nondeterministic Turing machines.
delta()
becomes deltaNTM()
and returns a list of triples (q',b,m). onestep()
becomes onestepNTM()
and returns now a list of configurations according to the possible transitions returned by deltaNTM()
. Notice, that it is key here to have a persistent data-structure for configurations, in order to make sure that the list of configurations after one step of computation are correct.simulation()
to simulationNTM()
in order to finally implement a recognizer for nondeterministic Turing machines. At first, you may consider to adapt the idea we discussed in class that consists of listing all the possible sequences of transitions, and simulating each sequence of transitions until one reaches the accept state. This solution is acceptable when the programming language available for the simulation is a Turing machine, but since we are in Python we may consider more advanced data-strucuture, and in particular using trees for the underlying computation tree of NTM. So instead, consider doing the simulation by build a tree of computation by iterative deepening which is an efficient search at the intersection of depth-first search and breadth-first search.
The recognizer should be a function that returns true is the input is recognized and otherwise returns false or is allowed to loop forever (until a stack overflow exception occurs).
def recognizer(ntm,w):
...
tm1 = TM(..., ..., ..., ...)
simulation()
is correct for 6 different, well chosen, input strings (3 accepted strings and 3 rejected strings).
b1 = simulation(tm1,w1)
b2 = simulation(tm1,w2)
...
b6 = simulation(tm1,w6)
ntm1 = TM(..., ..., ..., ...)
simulationNTM()
works correctly by testing it on 6 different input strings (3 strings that accept and 3 strings that rejects or loop forever).
c1 = simulationNTM(ntm1,v1)
c2 = simulationNTM(ntm1,v2)
...
c6 = simulationNTM(ntm1,v6)
csci341
git folder. Add, commit and push your work.