-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathTuring.py
More file actions
203 lines (162 loc) · 6.44 KB
/
Copy pathTuring.py
File metadata and controls
203 lines (162 loc) · 6.44 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
from dataclasses import dataclass
EMPTY = "b"
START_STATE = "Q0"
L = -1
R = 1
S = 0
@dataclass()
class TuringCommand:
"""
Stores a single command of the form: inp_state, inp_symbol -> ret_state, ret_symbol, ret_move.
The symbols need to have length exactly one and ret_move can be L=-1, R=1 or S=0.
"""
inp_state: str
inp_symbol: str
ret_state: str
ret_symbol: str
ret_move: L | R | S
def __post_init__(self):
if len(self.inp_symbol) != 1:
raise ValueError(f"Symbols need to have length one, but inp_symbol is {self.inp_symbol} with length "
f"{len(self.inp_symbol)}.")
if len(self.ret_symbol) != 1:
raise ValueError(f"Symbols need to have length one, but ret_symbol is {self.ret_symbol} with length "
f"{len(self.ret_symbol)}.")
if self.ret_move not in [L, R, S]:
raise ValueError(f"ret_moves needs to be either L=-1, R=1 or S=0, but is {self.ret_move}.")
@classmethod
def parse(cls, text: str) -> 'TuringCommand':
"""Creates a TuringCommand from its string representation."""
inp_state, inp_symbol, ret_state, ret_symbol, ret_move_str = text.split(" ")
match ret_move_str:
case "L":
ret_move = L
case "R":
ret_move = R
case "S":
ret_move = S
case _:
raise ValueError(f"The move of a command needs to be saved as either 'L', 'R' or 'S', but tried to "
f"parse '{ret_move_str}' in command '{text}'.")
return cls(inp_state, inp_symbol, ret_state, ret_symbol, ret_move)
@dataclass()
class TuringState:
"""Stores the state of a Turing machine and the tape."""
tape: list[str]
position: int = 0
offset: int = 0
state: str = START_STATE
@property
def symbol(self) -> str:
"""
Return the symbol at the position in the tape.
>>> ts = TuringState(["-2", "-1", "0", "1", "2", "3"], 2, 2)
>>> ts.symbol
'2'
"""
return self.tape[self.position + self.offset]
def execute_command(self, command: TuringCommand):
"""
Executes a single TuringCommand if its requirements are met, else raises an error.
>>> ts = TuringState(["-2", "-1", "0", "1", "2", "3"], 2, 2, "Q1")
>>> tc = TuringCommand("Q1", "2", "Q2", "3", L)
>>> ts.execute_command(tc)
>>> ts.tape
['-2', '-1', '0', '1', '3', '3']
>>> ts.position
1
>>> ts.state
'Q2'
>>> ts = TuringState(["0", "1"], 0, 0, "Q1")
>>> tc = TuringCommand("Q1", "0", "Q2", "0", L)
>>> ts.execute_command(tc)
>>> ts.tape
['b', '0', '1']
>>> ts.position
-1
>>> ts.offset
1
>>> ts = TuringState(["0", "1"], 1, 0, "Q1")
>>> tc = TuringCommand("Q1", "1", "Q2", "1", R)
>>> ts.execute_command(tc)
>>> ts.tape
['0', '1', 'b']
>>> ts.position
2
"""
if self.state == command.inp_state:
if self.symbol == command.inp_symbol:
self.tape[self.position + self.offset] = command.ret_symbol
self.state = command.ret_state
self.position += command.ret_move
# Handling out of tape bound movements
if self.position + self.offset < 0:
self.offset += 1
self.tape = [EMPTY] + self.tape
elif self.position + self.offset >= len(self.tape):
self.tape = self.tape + [EMPTY]
assert 0 <= self.position + self.offset < len(self.tape)
else:
raise ValueError(f"Command requires symbol {command.inp_symbol}, but current symbol is {self.symbol}.")
else:
raise ValueError(f"Command requires state {command.inp_state}, but current state is {self.state}.")
def __str__(self) -> str:
string = f"{''.join(self.tape)}\n{' ' * (self.position + self.offset)}^{self.state}"
return string
@dataclass()
class TuringMachine:
"""Stores a program of the form dict[(state, symbol): command]."""
programm: dict[(str, str): TuringCommand]
@classmethod
def parse(cls, text: str) -> 'TuringMachine':
"""Creates a TuringMachine from its string representation."""
programm = {}
lines = text.split("\n")
for line in lines:
if len(line) != 0:
command = TuringCommand.parse(line)
programm[(command.inp_state, command.inp_symbol)] = command
return cls(programm)
def single_step(self, turing_state: TuringState) -> bool:
"""
Returns False if the program contains no commands for the given turing_state, else executes a single fitting
command on the turing_state and returns True.
>>> ts = TuringState(["0", "1"], 0, 0, "Q1")
>>> tm = TuringMachine({("Q1", "0"): TuringCommand("Q1", "0", "Q2", "1", R)})
>>> tm.single_step(ts)
True
>>> ts.tape
['1', '1']
>>> tm = TuringMachine({})
>>> tm.single_step(ts)
False
>>> ts.tape
['1', '1']
"""
state, symbol = (turing_state.state, turing_state.symbol)
if (state, symbol) in self.programm:
command: TuringCommand = self.programm[(state, symbol)]
turing_state.execute_command(command)
return True
else:
return False
def run(self, tape: list[str], start_state: str = START_STATE, debug_strength: int = 0) -> (list[str], str):
"""
Runs the program on the given tape until it halts and returns the resulting tape and the final state.
The amount of debug output can be controlled through debug_strength, with: \n
0: no debug printing \n
1: Printing the final result \n
2: Printing results of every step
"""
turing_state = TuringState(tape, state=start_state)
run = True
while run:
if debug_strength >= 2:
print(turing_state)
run = self.single_step(turing_state)
if debug_strength >= 1:
print(f"Program finished on state {turing_state.state} and tape: \n{''.join(turing_state.tape)}")
return turing_state.tape, turing_state.state
if __name__ == "__main__":
import doctest
doctest.testmod()