-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathautomata.py
283 lines (240 loc) · 9.72 KB
/
automata.py
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
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
#!/usr/bin/env python3
from regex_parser import ILLEGAL, DFAState, regex_to_NFAb
import hypothesis.strategies as st
import itertools
class NFA:
def __init__(self, states, alphabet, initial, final):
self.states = states
self.alphabet = alphabet
self.initial = initial
self.final = final
def __str__(self):
L = ["\t".join([""] + [str(x) for x in self.alphabet] + ['\u03B5'] + ["-|", "|-"])]
for s in self.states:
L.append("\t".join(
[str(s)] +
[str(s.transitions.get(x, "")) for x in self.alphabet] +
[str(s.epsilon)] +
["yes" if str(s) in self.final else "",
"yes" if s == self.initial else ""]
))
return "\n".join(L)
def calcEclosure(self):
for s in self.states:
st_list = [s]
visited_list = [s]
while st_list != []:
curr = st_list.pop(0)
s.eclosure.append(curr)
st_list += [t for t in curr.epsilon if t not in st_list and t not in visited_list]
visited_list += [t for t in curr.epsilon if t not in visited_list]
def NFAtoDFA(self):
self.calcEclosure() # first and foremost calculate e-closure
state_list = []
last = []
is_first = True
dfa_states = [self.initial.eclosure]
incr = 0
while incr < len(dfa_states):
#get first element of list to check
current = dfa_states[incr]
dict = {}
is_last = False
for i in self.alphabet: #for every input find transitions
lista = []
for s in current: # for every state in the e-closure
trans = s.transitions.get(i)
if trans:
lista += trans.eclosure
if s.is_end:
is_last = True
#remove duplicates
lista = list(set(lista))
# this is transition for input 'i' for current state
dict[i] = lista
#only add to check unique states
if lista and lista not in dfa_states:
dfa_states.append(lista)
a = DFAState('t' + str(len(state_list)))
a.original = current #list of original nfa states
a.transitions = dict
a.is_end = is_last
if is_last:
last.append(a)
if is_first:
first = a
is_first = False
state_list.append(a)
incr += 1
for s in state_list:
for i in self.alphabet:
trans = s.transitions.get(i)
if trans:
j = dfa_states.index(trans)
s.transitions[i] = state_list[j]
else:
s.transitions[i] = ""
return DFA(state_list, self.alphabet, first, last)
class DFA:
def __init__(self, states, alphabet, initial, final):
self.states = states
self.alphabet = alphabet
self.initial = initial
self.final = final
self.transition = {s: {i: s.transitions.get(i)
for i in alphabet if s.transitions.get(i)} for s in states}
self.valid = {s: [i for i in alphabet if s.transitions.get(i)]
for s in states}
self.invalid = {s: [i for i in alphabet if not s.transitions.get(i)]
+ list(ILLEGAL)
for s in states}
def __str__(self):
L = ["\t".join([""] + [str(x) for x in self.alphabet] + ["-|", "|-"])]
for s in self.states:
L.append("\t".join(
[str(s)] +
[str(self.transition[s].get(x, "")) for x in self.alphabet] +
["yes" if s in self.final else "",
"yes" if s == self.initial else ""]
))
return "\n".join(L)
def minimize(self):
S = [] #same type
T = [] #different type
comb = itertools.combinations(self.states, 2)
for c in comb:
if (c[0] in self.final) == (c[1] in self.final):
S.append((c[0], c[1]))
else:
T.append((c[0], c[1]))
S.reverse() #reverse list to start from last #bug for 0110 due to (delta0, delta1) not yet in T
Sremove = []
for c in S:
for i in self.alphabet:
delta0 = self.transition[c[0]].get(i)
delta1 = self.transition[c[1]].get(i)
if (delta0, delta1) in T or (delta1, delta0) in T or (delta0 and not delta1) or (not delta0 and delta1):
Sremove.append(c)
T.append(c)
break
newS = [c for c in S if c not in Sremove] #all states left in S after removal
equals = {}
for st in self.states:
unpack = list(itertools.chain(*[c for c in newS if st in c]))
if unpack:
equals[st] = list(set(unpack)) #remove duplicates
else:
equals[st] = [st] #if state has no equals, it is equal to itself
# remove equals for all equal states but 1
for st in self.states:
if equals[st]:
for tmp in equals[st]:
if tmp != st:
equals[tmp] = []
min_state_list = [] #list of new states for minimized dfa
mindfa_origstates = [] #original states in the same order as min_state_list
for st in self.states:
if equals[st]:
a = DFAState('q' + str(len(min_state_list)))
a.original = equals[st] #list of original dfa states
a.transitions = {x: self.transition[s].get(x) for x in self.alphabet for s in a.original}
a.is_end = any([s.is_end for s in a.original])
min_state_list.append(a)
mindfa_origstates.append(a.original)
last = []
for s in min_state_list:
if s.is_end:
last.append(s)
if self.initial in s.original:
first = s
for i in self.alphabet:
trans = s.transitions.get(i)
if trans:
j, *_ = [k for k in range(len(mindfa_origstates)) if trans in mindfa_origstates[k]]
s.transitions[i] = min_state_list[j]
else:
s.transitions[i] = ""
return DFA(min_state_list, self.alphabet, first, last)
def accepts(self, input):
s = self.initial
for x in input:
s = self.transition[s].get(x)
if s == None: return False
return s in self.final
def generate(self, draw, valid=True, min_size=0, max_size=None):
transitions = draw(st.lists(st.just(None), min_size=min_size,
max_size=max_size))
s = self.initial
last_good = None
i = 0
while True:
x = None
assert s is not None or not valid
# Keep this state, if it is good, in case nothing better is found.
if valid and s in self.final or not valid and s not in self.final:
last_good = i
# If we're past the designated moves, stop with the last good state.
if last_good is not None and i >= len(transitions):
break
# If we're in a non-existent state (already generated a negative
# example), keep adding symbols.
elif s is None:
x = draw(st.sampled_from(self.alphabet))
# If we're good to stop with one final move, do it.
elif i >= len(transitions)-1:
if valid:
choices = sorted(x for x, t in self.transition[s].items()
if t in self.final)
else:
choices = sorted(x for x, t in self.transition[s].items()
if t not in self.final) + self.invalid[s]
if choices:
x = draw(st.sampled_from(choices))
# If there's nothing better, keep making valid moves.
if x is None and self.valid[s]:
x = draw(st.sampled_from(self.valid[s]))
# If there's nothing better and we need a negative example,
# make an invalid move.
if x is None and not valid and self.invalid[s]:
x = draw(st.sampled_from(self.invalid[s]))
# If there's no move to be made, give up.
if x is None:
break
# Register the move and proceed to the next state
if i < len(transitions):
transitions[i] = x
else:
transitions.append(x)
if s is not None:
s = self.transition[s].get(x)
i += 1
# If we failed, return None.
if last_good is None:
return None
# Return either the full list or its last good slice.
if last_good == len(transitions):
return transitions
else:
return transitions[:last_good]
def regex_to_DFA(regex):
nfa = NFA(*regex_to_NFAb(regex))
dfa = nfa.NFAtoDFA()
min_dfa = dfa.minimize()
return min_dfa
'''
if __name__ == '__main__':
while True:
regex = str(input())
nfa = NFA(*regex_to_NFAb(regex))
print("==== ΝFA ====")
print(nfa)
print()
dfa = nfa.NFAtoDFA()
print("==== DFA ====")
print(dfa)
print()
s = dfa.minimize()
print("==== min DFA ====")
print(s)
print()
'''