Skip to content

Commit b1ad0bf

Browse files
author
andrewbadr
committed
some renaming and cleanup
1 parent dfe9252 commit b1ad0bf

File tree

2 files changed

+82
-59
lines changed

2 files changed

+82
-59
lines changed

DFA.py

+80-58
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
# Contact: andrewbadr@gmail.com
66
# Code contributions are welcome.
77

8-
from debug import prints
98
from copy import copy
9+
from UnionFind import UnionFind
1010

1111
class DFA:
1212
"""This class represents a deterministic finite automaton."""
@@ -106,13 +106,9 @@ def state_hash(self, value):
106106
"""
107107
d = {}
108108
for state in self.states:
109-
if value == {}:
110-
d[state] = {}
111-
elif value == []:
112-
d[state] = []
113-
else:
114-
d[state] = value
109+
d[state] = copy(value)
115110
return d
111+
116112
def state_subset_hash(self, subset):
117113
"""Creates a hash with one key for every state in the DFA, with
118114
the value True for states in 'subset' and False for all others.
@@ -121,11 +117,11 @@ def state_subset_hash(self, subset):
121117
for q in subset:
122118
hash[q] = True
123119
return hash
120+
124121
def state_merge(self, q1, q2):
125122
"""Merges q1 into q2. All transitions to q1 are moved to q2.
126123
If q1 was the start or current state, those are also moved to q2.
127124
"""
128-
prints("Merging '%s' into '%s'" % (q1, q2))
129125
self.states.remove(q1)
130126
if q1 in self.accepts:
131127
self.accepts.remove(q1)
@@ -138,13 +134,11 @@ def state_merge(self, q1, q2):
138134
transitions[state] = {}
139135
for char in self.alphabet:
140136
next = self.delta(state, char)
141-
prints("Checking transition [%s][%s]->[%s]" % (state, char, next))
142137
if next == q1:
143-
prints("Changing transition [%s][%s]->[%s]" % (state, char, next))
144138
next = q2
145-
prints(" Now it's [%s][%s]->[%s]" % (state, char, next))
146139
transitions[state][char] = next
147140
self.delta = (lambda s, c: transitions[s][c])
141+
148142
def reachable_from(self, q0, inclusive=True):
149143
"""Returns the set of states reachable from given state q0. The optional
150144
parameter "inclusive" indicates that q0 should always be included.
@@ -161,9 +155,11 @@ def reachable_from(self, q0, inclusive=True):
161155
reached[next] = True
162156
to_process.append(next)
163157
return filter(lambda q: reached[q], self.states)
158+
164159
def reachable(self):
165160
"""Returns the reachable subset of the DFA's states."""
166161
return self.reachable_from(self.start)
162+
167163
def delete_unreachable(self):
168164
"""Deletes all the unreachable states."""
169165
reachable = self.reachable()
@@ -223,7 +219,6 @@ def collapse(self, partition):
223219
state_map = {}
224220
#build new_states, new_start, new_current_state:
225221
for state_class in partition:
226-
prints("Processing state class: %s" % state_class)
227222
representative = state_class[0]
228223
new_states.append(representative)
229224
for state in state_class:
@@ -253,19 +248,16 @@ def minimize(self):
253248
"""Classical DFA minimization, using the simple O(n^2) algorithm.
254249
Side effect: can mix up the internal ordering of states.
255250
"""
256-
prints("Starting states: %s" % self.states)
257251
#Step 1: Delete unreachable states
258252
self.delete_unreachable()
259-
prints("After deleting unreachable: %s" % self.states)
260253
#Step 2: Partition the states into equivalence classes
261254
classes = self.mn_classes()
262-
prints("Classes: %s" % classes)
263255
#Step 3: Construct the new DFA
264256
self.collapse(classes)
265-
prints("After collapsing: %s" % self.states)
266-
def find_fin_inf_parts(self):
267-
"""Returns the partition of the state-set into the finite-part and
268-
infinite-part as a 2-tuple. A state is in the finite part iff there
257+
258+
def preamble_and_kernel(self):
259+
"""Returns the partition of the state-set into the preamble and
260+
kernel as a 2-tuple. A state is in the preamble iff there
269261
are finitely many strings that reach it from the start state.
270262
271263
See "The DFAs of Finitely Different Regular Languages" for context.
@@ -279,9 +271,10 @@ def find_fin_inf_parts(self):
279271
if q in reachable[q]:
280272
for next in reachable[q]:
281273
in_fin[next] = False
282-
finite_part = filter(lambda x: in_fin[x], self.states)
283-
infinite_part = filter(lambda x: not in_fin[x], self.states)
284-
return (finite_part, infinite_part)
274+
preamble = filter(lambda x: in_fin[x], self.states)
275+
kernel = filter(lambda x: not in_fin[x], self.states)
276+
return (preamble, kernel)
277+
285278
def pluck_leaves(self):
286279
"""Only for minimized automata. Returns a topologically ordered list of
287280
all the states that induce a finite language. Runs in linear time.
@@ -319,66 +312,94 @@ def pluck_leaves(self):
319312
#prints("Adding '%s' to be plucked" % incoming)
320313
plucked.reverse()
321314
return plucked
315+
316+
def right_finite_states(self, sink_states):
317+
"""Given a DFA (self) and a list of states (sink_states) that are assumed to induce the
318+
empty language, return the topologically-ordered set of states in the DFA that induce
319+
finite languages.
320+
"""
321+
#Step 1: Build the states' profiles
322+
inbound = self.state_hash([])
323+
outbound = self.state_hash([])
324+
is_sink_state = self.state_subset_hash(sink_states)
325+
for state in self.states:
326+
if is_sink_state[state]:
327+
continue
328+
for c in self.alphabet:
329+
next = self.delta(state, c)
330+
inbound[next].append(state)
331+
outbound[state].append(next)
332+
333+
#Step 2: Pluck!
334+
to_pluck = sink_states
335+
plucked = []
336+
while len(to_pluck):
337+
state = to_pluck.pop()
338+
plucked.append(state)
339+
for incoming in inbound[state]:
340+
outbound[incoming].remove(state)
341+
if (len(outbound[incoming]) == 0) and (incoming != state):
342+
to_pluck.append(incoming)
343+
plucked.reverse()
344+
return plucked
345+
322346
def is_finite(self):
323347
"""Indicates whether the DFA's language is a finite set."""
324348
D2 = self.copy()
325349
D2.minimize()
326350
plucked = D2.pluck_leaves()
327351
return (D2.start in plucked)
352+
328353
def states_fd_equivalent(self, q1, q2):
329354
"""Indicates whether q1 and q2 only have finitely many distinguishing strings."""
330355
d1 = DFA(states=self.states, start=q1, accepts=self.accepts, delta=self.delta, alphabet=self.alphabet)
331356
d2 = DFA(states=self.states, start=q2, accepts=self.accepts, delta=self.delta, alphabet=self.alphabet)
332357
sd_dfa = symmetric_difference(d1, d2)
333358
return sd_dfa.is_finite()
334-
def fd_classes(self):
335-
"""Returns a partition of the states into finite-difference equivalence classes."""
359+
360+
def f_equivalence_classes(self):
361+
"""Returns a partition of the states into finite-difference equivalence clases, using
362+
the experimental O(n^2) algorithm."""
336363
sd = symmetric_difference(self, self)
337-
sd2 = sd.copy()
338-
classes = sd.mn_classes()
339-
state_map = sd2.collapse(classes)
340-
plucked = sd2.pluck_leaves()
341-
plucked_h = sd2.state_subset_hash(plucked)
342-
similar_states_list = filter(lambda q: plucked_h[state_map[q]], sd.states)
343-
similar_states = sd.state_subset_hash(similar_states_list)
344-
state_classes = []
364+
self_pairs = [(x, x) for x in self.states]
365+
fd_equiv_pairs = sd.right_finite_states(self_pairs)
366+
sets = UnionFind()
345367
for state in self.states:
346-
placed = False
347-
for sc in state_classes:
348-
rep = sc[0]
349-
if similar_states[(state,rep)]:
350-
sc.append(state)
351-
placed = True
352-
break #only for speed, not logic -- like how I live
353-
if not placed:
354-
state_classes.append([state])
368+
sets.make_set(state)
369+
for (state1, state2) in fd_equiv_pairs:
370+
set1, set2 = sets.find(state1), sets.find(state2)
371+
if set1 != set2:
372+
sets.union(set1, set2)
373+
state_classes = sets.as_lists()
355374
return state_classes
356-
def finite_difference_minimize(self):
375+
376+
def hyper_minimize(self):
357377
"""Alters the DFA into a smallest possible DFA recognizing a finitely different language.
358378
In other words, if D is the original DFA and D' the result of this function, then the
359379
symmetric difference of L(D) and L(D') will be a finite set, and there exists no smaller
360380
automaton than D' with this property.
361381
362382
See "The DFAs of Finitely Different Regular Languages" for context.
363383
"""
364-
#Step 1: Classical minimization
384+
# Step 1: Classical minimization
365385
self.minimize()
366-
#Step 2: Partition states into equivalence classes
367-
state_classes = self.fd_classes()
368-
#Step 3: Find finite and infinite parts
369-
(fin_part, inf_part) = self.find_fin_inf_parts()
370-
#Step 4: Merge
386+
# Step 2: Partition states into equivalence classes
387+
state_classes = self.f_equivalence_classes()
388+
# Step 3: Find preamble and kernel parts
389+
(preamble, kernel) = self.preamble_and_kernel()
390+
# Step 4: Merge (f_merge_states in the paper)
391+
# (Could be done more efficiently)
371392
for sc in state_classes:
372-
fins = filter(lambda s: s in fin_part, sc)
373-
infs = filter(lambda s: s in inf_part, sc)
374-
if len(infs) != 0:
375-
rep = infs[0]
376-
for fp_state in fins:
377-
self.state_merge(fp_state, rep)
393+
pres = filter(lambda s: s in preamble, sc)
394+
kers = filter(lambda s: s in kernel, sc)
395+
if len(kers):
396+
rep = kers[0]
397+
for p_state in pres:
398+
self.state_merge(p_state, rep)
378399
else:
379-
rep = fins[0]
380-
for fp_state in fins[1:]:
381-
self.state_merge(fp_state, rep)
400+
rep = pres[0]
401+
for p_state in pres[1:]:
402+
self.state_merge(p_state, rep)
382403
def levels(self):
383404
"""Returns a dictionary mapping each state to its distance from the starting state."""
384405
levels = {}
@@ -417,6 +438,7 @@ def long_path(q,length, longest):
417438
longest = candidate
418439
return longest
419440
return long_path(self.start, 0, None)
441+
420442
def DFCA_minimize(self, l=None):
421443
"""DFCA minimization"
422444
Input: "self" is a DFA accepting a finite language
@@ -601,7 +623,7 @@ def finite_factor(self):
601623
D1 = self.copy()
602624
D1.minimize()
603625
D2 = D1.copy()
604-
D2.finite_difference_minimize()
626+
D2.hyper_minimize()
605627
D3 = symmetric_difference(D1, D2)
606628
l = D3.DFCA_minimize()
607629
return (D2, (D3, l))

demo.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import DFA
2+
#5 / 10 8 7 3
23

34
#Basics:
45
states = range(5)
@@ -48,6 +49,6 @@ def delta(state, char):
4849
e.pretty_print()
4950
#raw_input()
5051
print "==...then Finite-Difference Minimized==="
51-
e.finite_difference_minimize()
52+
e.hyper_minimize()
5253
e.pretty_print()
5354
#raw_input()

0 commit comments

Comments
 (0)