Commit ed41bb02 authored by Kateřina Sloupová's avatar Kateřina Sloupová
Browse files

DFA equivalence and emptiness; NFA can epsilon

parent e7408f3b
Pipeline #54301 passed with stage
in 25 seconds
from parser import Parser
from reg_automata import Character, State, DFA, NFA
from reg_automata import Character, State, DFA, NFA, NFAE, Eps
from reg_automata import Composition as comp
from reg_grammars import REG, Terminal, Nonterminal
from typing import Set, Dict, Tuple, Union
from copy import deepcopy
def make_dfa(states: Set[str], characters: Set[str],
......@@ -66,6 +67,23 @@ def nfa_1():
nfa = NFA({q0, q1, q2}, {a}, transition, q0, {q1, q2})
return nfa
def nfa_e():
q0 = State("q0")
q1 = State("q1")
q2 = State("q2")
q3 = State("q3")
q4 = State("q4")
a = Character("a")
b = Character("b")
c = Character("c")
d = Character("d")
e = Eps()
transition: Dict[Tuple[State, Union[Character, Eps]], Set[State]] = \
{(q0, e): {q1}, (q1, a): {q0}, (q1, d): {q3}, (q1, e): {q2},
(q2, a): {q3}, (q3, e): {q4}, (q4, b): {q3}, (q4, c): {q2}}
nfae = NFAE({q0, q1, q2, q3, q4}, {a, b, c, d}, transition, q0, {q2})
return nfae
def main():
dfa_1 = make_dfa({"q_0", "q_1"}, {"a"}, {("q_0", "a"): "q_1", ("q_1", "a"): "q_1"}, "q_0", {"q_1"})
dfa_2 = make_dfa({"r_0", "r_1", "r_2"}, {"a", "b"}, {("r_0", "a"): "r_1", ("r_0", "b"): "r_1",
......@@ -138,17 +156,18 @@ def main():
print("DFA.eliminate_unreachable()")
print("Add new state (also to transition) and eliminate unreachable states.")
dfa_x = deepcopy(dfa_1)
q_x = State("q_x")
a = Terminal("a")
dfa_1.states.add(q_x)
dfa_1.final.add(q_x)
dfa_1.transition[q_x, a] = q_x
dfa_x.states.add(q_x)
dfa_x.final.add(q_x)
dfa_x.transition[q_x, a] = q_x
print()
print("DFA with new state:")
print(parser.dfa_to_str(dfa_1, True))
print(parser.dfa_to_str(dfa_x, True))
print()
print("DFA without unreachable states:")
eliminated = dfa_1.eliminate_unreachable()
eliminated = dfa_x.eliminate_unreachable()
print(parser.dfa_to_str(eliminated, True))
print()
......@@ -158,7 +177,6 @@ def main():
print()
print(parser.dfa_to_str(dfa_3, True))
print()
print("Minimal DFA:")
minimal = dfa_3.minimize()
......@@ -191,6 +209,15 @@ def main():
print(parser.nfa_to_str(regdfa, True))
print()
print("__________________________________")
print("DFA.is_empty(), DFA.is_equivalent()")
print("Emptiness with counterexample and equivalence \n"
"of DFA through intersections with complements.")
print()
print("is dfa3 empty?", dfa_3.is_empty())
print("dfa3 and canonical dfa3: ", dfa_3.is_equivalent(canonical), ", should be True")
print()
print("__________________________________")
print("NFA.determinize()")
print("Determinization of NFA to DFA:")
......@@ -200,12 +227,12 @@ def main():
print(parser.dfa_to_str(det, True))
print("__________________________________")
print("DFA.is_equivalent()")
print("Equivalence of DFAs through intersections with complements.")
print("NFAE.eliminate_epsilon()")
print("Elimination of epsilon steps in NFA.")
nfae = nfa_e()
print(parser.nfa_to_str(nfae))
print()
print(dfa_3.is_equivalent(canonical), ", should be True")
print()
nfa_elim = nfae.eliminate_epsilon()
print(parser.nfa_to_str(nfa_elim, True))
main()
\ No newline at end of file
This diff is collapsed.
from typing import List, Dict, Tuple, Optional
from typing import List, Dict, Tuple, Optional, Union
import re
from reg_automata import DFA, NFA
from reg_automata import DFA, NFA, NFAE, Eps
from reg_grammars import REG, Terminal
......@@ -89,7 +89,7 @@ class Parser:
return transition + "\n" + final
def nfa_to_str(self, nfa: NFA, full : bool = False) -> str:
def nfa_to_str(self, nfa: Union[NFA, NFAE], full : bool = False) -> str:
# full - verbose description of DFA - only for development, dismiss later
......@@ -108,7 +108,11 @@ class Parser:
transition = ""
for key, set_states in nfa.transition.items():
state_1, terminal = key
transition += "(" + state_1.name + "," + terminal.name + ")={"
if isinstance(terminal, Eps):
char = "epsilon"
else:
char = terminal.name
transition += "(" + state_1.name + "," + char + ")={"
for state in set_states:
transition += state.name + ","
transition = transition[:-1] + "}\n"
......@@ -128,7 +132,8 @@ class Parser:
else:
return transition + "\n" + final
# TODO DFA/NFA to string are too similar
def str_to_dfa(self, input: str):
pass
# all: string <-> formal object
# reg
......
......@@ -26,8 +26,14 @@ class Character:
class Eps:
pass
def __init__(self):
pass
def __eq__(self, other):
return isinstance(other, Eps)
def __hash__(self):
return hash("epsilon")
class State:
def __init__(self, name: str):
......@@ -46,31 +52,20 @@ class DFA:
Transition = Dict[Tuple[State, Character], State]
type_var = TypeVar('type_var')
def set_or_none(self, out: Optional[Set[type_var]]) -> Set[type_var]:
return set() if out is None else out
def dict_or_none(self, out: Optional[Transition]) -> Transition:
return dict() if out is None else out
def __init__(self, states: Set[State],
characters: Set[Character],
transition: Transition,
init: State,
final: Set[State]):
characters: Set[Character],
transition: Transition,
init: State,
final: Set[State]):
self.states = self.set_or_none(states)
self.characters = self.set_or_none(characters)
self.transition = self.dict_or_none(transition)
self.states = states
self.characters = characters
self.transition = transition
self.init = init
self.final = self.set_or_none(final)
self.final = final
assert self.check()
def check(self) -> bool:
# # scheme of automaton - empty sets and dicts are implemention only variant
# if len(self.states) == 0:
# assert len(self.characters) == 0 and len(self.transition) == 0 and self.init == None \
# and len(self.final) == 0, "nonempty scheme of automaton"
# return True
assert len(self.states) > 0, "empty automaton"
for (state, character) in self.transition:
......@@ -117,10 +112,17 @@ class DFA:
return False
return True
def accepts(self, word: str) -> bool:
def accepts(self, word: Union[str, List[Character]]) -> bool:
if isinstance(word, str):
evaluate = []
for letter in word:
character = Character(letter)
evaluate.append(character)
else:
evaluate = word
state = self.init
for letter in word:
character = Character(letter)
for character in evaluate:
if (state, character) not in self.transition:
return False
state = self.transition[state, character]
......@@ -137,7 +139,6 @@ class DFA:
@staticmethod
def composition(dfa_1: DFA, dfa_2: DFA, operation: Composition) -> DFA:
# 2019 IB102 slides want both total
if not dfa_1.is_total():
dfa_1 = dfa_1.total()
if not dfa_2.is_total():
......@@ -162,10 +163,10 @@ class DFA:
for character in characters:
if (state_1, character) in dfa_1.transition and \
(state_2, character) in dfa_2.transition:
move_1 = dfa_1.transition[state_1, character]
move_2 = dfa_2.transition[state_2, character]
move = State(move_1.name + "_" + move_2.name)
transition[state, character] = move
dest_state_1 = dfa_1.transition[state_1, character]
dest_state_2 = dfa_2.transition[state_2, character]
dest_state = State(dest_state_1.name + "_" + dest_state_2.name)
transition[state, character] = dest_state
# new final
final: Set[State] = set()
......@@ -248,8 +249,7 @@ class DFA:
for state in dfa.states:
pattern = [previous[state]]
for character in characters:
result = dfa.transition[state, character]
pattern.append(previous[result])
pattern.append(previous[dfa.transition[state, character]])
if tuple(pattern) in patterns:
patterns[tuple(pattern)].add(state)
......@@ -271,8 +271,8 @@ class DFA:
for character in characters:
if not (new_state, character) in new_transition:
originally = dfa.transition[state, character]
now = State("new_" + str(actual[originally]))
old = dfa.transition[state, character]
now = State("new_" + str(actual[old]))
new_transition[new_state, character] = now
if state == self.init:
......@@ -348,25 +348,25 @@ class DFA:
nonterminals.add(init)
for state, character in self.transition:
move = self.transition[state, character]
dest_state = self.transition[state, character]
if state == self.init:
nonterminal2 = Nonterminal(move.name)
nonterminal2 = Nonterminal(dest_state.name)
terminal = Terminal(character.name)
if init in rules:
rules[init].add((terminal, nonterminal2))
else:
rules[init] = {(terminal, nonterminal2)}
if move in self.final:
if dest_state in self.final:
rules[init].add(terminal)
else:
nonterminal1 = Nonterminal(state.name)
nonterminal2 = Nonterminal(move.name)
nonterminal2 = Nonterminal(dest_state.name)
terminal = Terminal(character.name)
if nonterminal1 in rules:
rules[nonterminal1].add((terminal, nonterminal2))
else:
rules[nonterminal1] = {(terminal, nonterminal2)}
if move in self.final:
if dest_state in self.final:
rules[nonterminal1].add((terminal))
reg = REG(nonterminals, terminals, rules, init)
......@@ -378,52 +378,59 @@ class DFA:
def is_empty(self) -> bool:
reached: Deque[State] = deque([self.init])
reachable: Set[State] = {self.init}
predecessor: Dict[State, Tuple[State, Character]] = {}
while len(reached) > 0:
actual = reached.popleft()
for character in self.characters:
if self.transition[actual, character] is not None and \
self.transition[actual, character] not in reachable:
reached.append(self.transition[actual, character])
reachable.add(self.transition[actual, character])
return len(reachable.intersection(self.final)) > 0
if (actual, character) in self.transition:
dest_state = self.transition[actual, character]
if dest_state not in reachable:
reached.append(self.transition[actual, character])
reachable.add(self.transition[actual, character])
predecessor[dest_state] = (actual, character)
if len(reachable.intersection(self.final)) == 0:
return True
else:
word = []
state = reachable.intersection(self.final).pop()
while state is not self.init:
word.append(predecessor[state][1].name)
state = predecessor[state][0]
counterexample = ''.join(reversed(word))
return False
# Ha!
def is_universal(self):
return self.complement().is_empty()
def is_equivalent(self, dfa: DFA) -> bool:
self.complement()
dfa.complement()
self.complement().is_empty()
dfa.complement().is_empty()
return self.intersection(self, dfa.complement()).is_empty() and \
self.intersection(self.complement(), dfa).is_empty()
def is_part_identical(self, dfa: DFA) -> bool:
return self.states == dfa.states and self.characters == dfa.characters and \
self.transition == dfa.transition and self.init == dfa.init and \
self.final == dfa.final
class NFA:
Transition = Dict[Tuple[State, Character], Set[State]]
type_var = TypeVar('type_var')
def set_or_none(self, out: Optional[Set[type_var]]) -> Set[type_var]:
return set() if out is None else out
def dict_or_none(self, out: Optional[Transition]) -> Transition:
return dict() if out is None else out
def __init__(self, states: Set[State],
characters: Set[Character],
transition: Transition,
init: State,
final: Set[State]):
self.states = self.set_or_none(states)
self.characters = self.set_or_none(characters)
self.transition = self.dict_or_none(transition)
self.states = states
self.characters = characters
self.transition = transition
self.init = init
self.final = self.set_or_none(final)
self.final = final
def determinize(self) -> DFA:
states: Set[FrozenSet[State]] = set()
......@@ -464,15 +471,61 @@ class NFA:
def unset(self, states: FrozenSet[State]) -> State:
return State('_'.join(set(map(lambda x: x.name, sorted(states, key=lambda x: x.name)))))
# would be function for in epsilon NFA
class NFAE:
Transition = Dict[Tuple[State, Union[Character, Eps]], Set[State]]
type_var = TypeVar('type_var')
def __init__(self, states: Set[State],
characters: Set[Character],
transition: Transition,
init: State,
final: Set[State]):
self.states = states
self.characters = characters
self.transition = transition
self.init = init
self.final = final
def eliminate_epsilon(self) -> NFA:
nfa = NFA(self.states, self.characters, {}, self.init, set())
# rozsirena prechodova funkce: D_eps(p) je podmnozina stavů X tz p tam je
# a pokud q je v X a d(q, eps) = r, pak r je v X
# a jde to pro mnoziny stavu (sjednoceni vsech)
surroundings: Dict[State, Set[State]] = {}
for state in self.states:
surroundings[state] = self.epsilon_surroundings(state)
new_transition: Dict[Tuple[State, Character], Set[State]] = {}
for state in self.states:
for character in self.characters:
reached_states = set()
if (state, character) in self.transition:
reached_states.update(self.transition[state, character])
for eps_state in surroundings[state]:
if (eps_state, character) in self.transition:
reached_states.update(self.transition[eps_state, character])
if len(reached_states) > 0:
new_transition[state, character] = deepcopy(reached_states)
for reached in reached_states:
new_transition[state, character].update(surroundings[reached])
new_final = deepcopy(self.final)
if surroundings[self.init].intersection(self.final):
new_final.add(self.init)
nfa = NFA(self.states, self.characters, new_transition, self.init, new_final)
return nfa
def epsilon_surroundings(self, state: State) -> Set[State]:
pass
reached: Deque[State] = deque([state])
reachable: Set[State] = {state}
eps = Eps()
while len(reached) > 0:
actual = reached.popleft()
if (actual, eps) in self.transition:
for dest_state in self.transition[actual, eps]:
if dest_state not in reachable:
reached.append(dest_state)
reachable.add(dest_state)
return reachable
\ No newline at end of file
......@@ -44,18 +44,10 @@ class REG:
self.terminals = terminals
self.rules = rules
self.init = init
assert self.check
assert self.check()
# TODO ask about concept
def check(self) -> bool:
# scheme of grammar - empty sets and dicts are implemention only variant
if len(self.nonterminals) == 0:
assert len(self.terminals) == 0 and len(self.rules) == 0 and self.init == None, "nonempty scheme of grammar"
return True
nonterminal_names = set(map(lambda x: x.name, self.nonterminals))
terminal_names = set(map(lambda x: x.name, self.terminals))
assert len(nonterminal_names.intersection(terminal_names)) == 0, "name conflict"
assert len(self.nonterminals) > 0, "empty grammar"
for nonterminal in self.rules:
assert nonterminal in self.nonterminals, "unknown nonterminal " + nonterminal.name
......
Supports Markdown
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