Skip to content

Commit 3b029cf

Browse files
authored
fix: Fix bug in minify edge case (#218)
1 parent 530016a commit 3b029cf

File tree

2 files changed

+45
-2
lines changed

2 files changed

+45
-2
lines changed

automata/fa/dfa.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,7 @@ def to_complete(self, trap_state: Optional[DFAStateT] = None) -> Self:
338338
An equivalent complete DFA.
339339
340340
"""
341+
341342
if not self.allow_partial:
342343
return self.copy()
343344

@@ -624,7 +625,9 @@ def _minify(
624625
x for x in count(-1, -1) if x not in reachable_states
625626
)
626627
for trap_symbol in input_symbols:
627-
transition_back_map[trap_symbol][trap_state] = []
628+
transition_back_map[trap_symbol][trap_state] = [
629+
trap_state
630+
]
628631

629632
reachable_states.add(trap_state)
630633

@@ -681,6 +684,11 @@ def _minify(
681684
if trap_state not in eq
682685
}
683686

687+
# If only one equivalence class with the trap state,
688+
# return empty language.
689+
if not back_map:
690+
return cls.empty_language(input_symbols)
691+
684692
new_input_symbols = input_symbols
685693
new_states = frozenset(back_map.values())
686694
new_initial_state = back_map[initial_state]
@@ -1707,13 +1715,18 @@ def from_prefix(
17071715

17081716
states = frozenset(transitions.keys())
17091717
final_states = {last_state}
1718+
1719+
is_partial = any(
1720+
len(lookup) != len(input_symbols) for lookup in transitions.values()
1721+
)
1722+
17101723
return cls(
17111724
states=states,
17121725
input_symbols=input_symbols,
17131726
transitions=transitions,
17141727
initial_state=0,
17151728
final_states=final_states if contains else states - final_states,
1716-
allow_partial=as_partial,
1729+
allow_partial=is_partial,
17171730
)
17181731

17191732
@classmethod

tests/test_dfa.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,13 @@ def test_to_complete_trap_state_exception(self) -> None:
293293
with self.assertRaises(exceptions.InvalidStateError):
294294
self.partial_dfa.to_complete(0)
295295

296+
def test_to_complete_no_extra_state(self) -> None:
297+
"""Should not add an extra state if DFA is complete."""
298+
alphabet = ["d", "e", "g", "h", "i", "k", "o", "t", "x"]
299+
substring = "ti"
300+
dfa = DFA.from_prefix(set(alphabet), substring, contains=False)
301+
self.assertEqual(dfa.states, dfa.to_complete().states)
302+
296303
def test_equivalence_not_equal(self) -> None:
297304
"""Should not be equal."""
298305
self.assertNotEqual(self.no_consecutive_11_dfa, self.zero_or_one_1_dfa)
@@ -308,6 +315,29 @@ def test_equivalence_minify(self) -> None:
308315
minimal_dfa = self.no_consecutive_11_dfa.minify()
309316
self.assertEqual(self.no_consecutive_11_dfa, minimal_dfa)
310317

318+
other_dfa = DFA(
319+
states={0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10},
320+
input_symbols={"a", "c", "b"},
321+
transitions={
322+
0: {"b": 1, "a": 2},
323+
1: {"b": 3, "a": 4, "c": 5},
324+
2: {"b": 6, "c": 5, "a": 4},
325+
3: {"b": 7, "c": 8},
326+
4: {"b": 9, "c": 10},
327+
5: {"b": 9, "c": 10},
328+
6: {"b": 9, "c": 10},
329+
7: {"b": 7, "c": 8},
330+
8: {"b": 9, "c": 10},
331+
9: {"c": 10, "b": 9},
332+
10: {"c": 10, "b": 9},
333+
},
334+
initial_state=0,
335+
final_states={2, 7, 8, 9, 10},
336+
allow_partial=True,
337+
)
338+
339+
self.assertEqual(other_dfa, other_dfa.minify())
340+
311341
def test_equivalence_two_non_minimal(self) -> None:
312342
"""Should be equivalent even though they are non minimal."""
313343

0 commit comments

Comments
 (0)