Commit c4aad1ac authored by Vladimír Štill's avatar Vladimír Štill
Browse files

dfa: Fix is_canonical

parent df5aa4c3
......@@ -311,29 +311,47 @@ class DFA:
name = ascii_uppercase[num - 1] + name
return name
def canonize(self) -> DFA:
@staticmethod
def canonic_name_for_seq(num: int) -> str:
# alternatively, use self.bijective26(i) for base-26 alpha. names
return str(num)
def _can_walk(self, state_callback: Callable[None, [State, State]]) -> Set[State]:
seen: Set[State] = set()
characters = self.sorted_characters()
state_map: Dict[State, State] = {}
i = 1
queue = deque([self.init])
while len(queue) > 0:
actual = queue.popleft()
if actual in state_map.keys():
if actual in seen:
continue
# alternatively, use self.bijective26(i) for base-26 alpha. names
state_map[actual] = State(str(i))
seen.add(actual)
state_callback(actual, State(self.canonic_name_for_seq(i)))
i += 1
for character in characters:
tgt = self.transition.get((actual, character))
if tgt is not None and tgt not in state_map.keys():
if tgt is not None and tgt not in seen:
queue.append(tgt)
return seen
def canonize(self) -> DFA:
"""
Returns a canonical automaton for ‹self›. Does NOT minimize the
automata, therefore canonize is useful for testing isomorfism, for
testing equivalence you should use ‹is_equivalent› or
‹minimize().canonize()›.
Precondition: ‹self› has no unreachable states (exception thrown otherwise).
"""
state_map: Dict[State, State] = {}
def add(old: State, new: State):
state_map[old] = new
seen = self._can_walk(add)
for st, _ in self.transition.keys():
if st not in state_map:
raise Exception("Automaton cannot be canonised, it has "
"unreachable states")
if self.states - seen:
raise Exception("Automaton cannot be canonised, it has "
"unreachable states")
return DFA(set(state_map.values()),
deepcopy(self.characters),
......@@ -460,7 +478,11 @@ class DFA:
return result
@staticmethod
def is_part_identical(fst: DFA, snd: DFA) -> bool:
def is_identical(fst: DFA, snd: DFA) -> bool:
"""
Check that two automata are identical, i.e. they are the same including
names.
"""
return fst.states == snd.states and fst.characters == snd.characters and \
fst.transition == snd.transition and fst.init == snd.init and \
fst.final == snd.final
......@@ -471,8 +493,16 @@ class DFA:
len(self.transition) == len(minimal.transition)
def is_canonical(self) -> bool:
canonic = self.canonize()
return DFA.is_part_identical(self, canonic)
# note: not all DFAs have canonic form and therefore it is not possible
# to test if automaton is canonic by canonising it and testing for
# identity. In particular canonize() needs the automaton to have no
# unreachable states to succeed.
is_can = True
def check(old: State, new: State) -> None:
nonlocal is_can
is_can = is_can and old.name == new.name
seen = self._can_walk(check)
return is_can and self.states == seen
def hell(self, states):
# check: consider import itertools
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment