Skip to content

Commit cbfe730

Browse files
authored
Merge pull request #176 from damhiya/improve-dfa-minimization
renew groupEquivStates
2 parents 35f07a1 + 9f399ae commit cbfe730

File tree

1 file changed

+86
-53
lines changed

1 file changed

+86
-53
lines changed

src/DFAMin.hs

Lines changed: 86 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -16,24 +16,53 @@ import Data.IntMap (IntMap)
1616
import qualified Data.IntMap as IM
1717
import qualified Data.List as List
1818

19-
20-
-- Hopcroft's Algorithm for DFA minimization (cut/pasted from Wikipedia):
21-
19+
-- % Hopcroft's Algorithm for DFA minimization (cut/pasted from Wikipedia):
20+
-- % X refines Y into Y1 and Y2 means
21+
-- % Y1 := Y ∩ X
22+
-- % Y2 := Y \ X
23+
-- % where both Y1 and Y2 are nonempty
24+
--
2225
-- P := {{all accepting states}, {all nonaccepting states}};
2326
-- Q := {{all accepting states}};
2427
-- while (Q is not empty) do
2528
-- choose and remove a set A from Q
2629
-- for each c in ∑ do
2730
-- let X be the set of states for which a transition on c leads to a state in A
28-
-- for each set Y in P for which X Y is nonempty do
29-
-- replace Y in P by the two sets X ∩ Y and Y \ X
31+
-- for each set Y in P for which X refines Y into Y1 and Y2 do
32+
-- replace Y in P by the two sets Y1 and Y2
3033
-- if Y is in Q
3134
-- replace Y in Q by the same two sets
3235
-- else
3336
-- add the smaller of the two sets to Q
3437
-- end;
3538
-- end;
3639
-- end;
40+
--
41+
-- % X is a preimage of A under transition function.
42+
43+
-- % observation : Q is always subset of P
44+
-- % let R = P \ Q. then following algorithm is the equivalent of the Hopcroft's Algorithm
45+
--
46+
-- R := {{all nonaccepting states}};
47+
-- Q := {{all accepting states}};
48+
-- while (Q is not empty) do
49+
-- choose a set A from Q
50+
-- remove A from Q and add it to R
51+
-- for each c in ∑ do
52+
-- let X be the set of states for which a transition on c leads to a state in A
53+
-- for each set Y in R for which X refines Y into Y1 and Y2 do
54+
-- replace Y in R by the greater of the two sets Y1 and Y2
55+
-- add the smaller of the two sets to Q
56+
-- end;
57+
-- for each set Y in Q for which X refines Y into Y1 and Y2 do
58+
-- replace Y in Q by the two sets Y1 and Y2
59+
-- end;
60+
-- end;
61+
-- end;
62+
--
63+
-- % The second for loop that iterates over R mutates Q,
64+
-- % but it does not affect the third for loop that iterates over Q.
65+
-- % Because once X refines Y into Y1 and Y2, Y1 and Y2 can't be more refined by X.
3766

3867
minimizeDFA :: forall a. Ord a => DFA Int a -> DFA Int a
3968
minimizeDFA dfa@(DFA { dfa_start_states = starts,
@@ -93,7 +122,7 @@ type EquivalenceClass = IntSet
93122

94123
groupEquivStates :: forall a. Ord a => DFA Int a -> [EquivalenceClass]
95124
groupEquivStates DFA { dfa_states = statemap }
96-
= go init_p init_q
125+
= go init_r init_q
97126
where
98127
accepting, nonaccepting :: Map Int (State Int a)
99128
(accepting, nonaccepting) = Map.partition acc statemap
@@ -112,57 +141,61 @@ groupEquivStates DFA { dfa_states = statemap }
112141
accept_groups :: [EquivalenceClass]
113142
accept_groups = map IS.fromList (Map.elems accept_map)
114143

115-
init_p, init_q :: [EquivalenceClass]
116-
init_p -- Issue #71: each EquivalenceClass needs to be a non-empty set
117-
| IS.null nonaccepting_states = accept_groups
118-
| otherwise = nonaccepting_states : accept_groups
144+
init_r, init_q :: [EquivalenceClass]
145+
init_r -- Issue #71: each EquivalenceClass needs to be a non-empty set
146+
| IS.null nonaccepting_states = []
147+
| otherwise = [nonaccepting_states]
119148
init_q = accept_groups
120149

121-
-- map token T to
122-
-- a map from state S to the list of states that transition to
150+
-- a map from token T to
151+
-- a map from state S to the set of states that transition to
123152
-- S on token T
124-
-- This is a cache of the information needed to compute x below
125-
bigmap :: IntMap (IntMap [SNum])
126-
bigmap = IM.fromListWith (IM.unionWith (++))
127-
[ (i, IM.singleton to [from])
153+
-- This is a cache of the information needed to compute xs below
154+
bigmap :: IntMap (IntMap EquivalenceClass)
155+
bigmap = IM.fromListWith (IM.unionWith IS.union)
156+
[ (i, IM.singleton to (IS.singleton from))
128157
| (from, state) <- Map.toList statemap,
129158
(i,to) <- IM.toList (state_out state) ]
130159

131-
-- incoming I A = the set of states that transition to a state in
132-
-- A on token I.
133-
incoming :: Int -> IntSet -> IntSet
134-
incoming i a = IS.fromList (concat ss)
135-
where
136-
map1 = IM.findWithDefault IM.empty i bigmap
137-
ss = [ IM.findWithDefault [] s map1
138-
| s <- IS.toList a ]
139-
140-
-- The outer loop: recurse on each set in Q
141-
go :: [EquivalenceClass] -> [EquivalenceClass] -> [EquivalenceClass]
142-
go p [] = p
143-
go p (a:q) = go1 0 p q
144-
where
145-
-- recurse on each token (0..255)
146-
go1 256 p q = go p q
147-
go1 i p q = go1 (i+1) p' q'
160+
-- The outer loop: recurse on each set in R and Q
161+
go :: [EquivalenceClass] -> [EquivalenceClass] -> [EquivalenceClass]
162+
go r [] = r
163+
go r (a:q) = uncurry go $ List.foldl' go0 (a:r,q) xs
164+
where
165+
xs :: [EquivalenceClass]
166+
xs =
167+
[ x
168+
| preimageMap <- IM.elems bigmap
169+
#if MIN_VERSION_containers(0, 6, 0)
170+
, let x = IS.unions (IM.restrictKeys preimageMap a)
171+
#else
172+
, let x = IS.unions [IM.findWithDefault IS.empty s preimageMap | s <- IS.toList a]
173+
#endif
174+
, not (IS.null x)
175+
]
176+
177+
refineWith
178+
:: IntSet -- preimage set that bisects the input equivalence class
179+
-> IntSet -- initial equivalence class
180+
-> Maybe (IntSet, IntSet)
181+
refineWith x y =
182+
if IS.null y1 || IS.null y2
183+
then Nothing
184+
else Just (y1, y2)
185+
where
186+
y1 = IS.intersection y x
187+
y2 = IS.difference y x
188+
189+
go0 (r,q) x = go1 r [] []
148190
where
149-
(p',q') = go2 p [] q
150-
151-
x = incoming i a
152-
153-
-- recurse on each set in P
154-
go2 [] p' q = (p',q)
155-
go2 (y:p) p' q
156-
| IS.null i || IS.null d = go2 p (y:p') q
157-
| otherwise = go2 p (i:d:p') q1
158-
where
159-
i = IS.intersection x y
160-
d = IS.difference y x
161-
162-
q1 = replaceyin q
163-
where
164-
replaceyin [] =
165-
if IS.size i < IS.size d then [i] else [d]
166-
replaceyin (z:zs)
167-
| z == y = i : d : zs
168-
| otherwise = z : replaceyin zs
191+
go1 [] r' q' = (r', go2 q q')
192+
go1 (y:r) r' q' = case refineWith x y of
193+
Nothing -> go1 r (y:r') q'
194+
Just (y1, y2)
195+
| IS.size y1 <= IS.size y2 -> go1 r (y2:r') (y1:q')
196+
| otherwise -> go1 r (y1:r') (y2:q')
197+
198+
go2 [] q' = q'
199+
go2 (y:q) q' = case refineWith x y of
200+
Nothing -> go2 q (y:q')
201+
Just (y1, y2) -> go2 q (y1:y2:q')

0 commit comments

Comments
 (0)