Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in
Toggle navigation
Menu
Open sidebar
fja
eval
Commits
ed41bb02
Commit
ed41bb02
authored
Mar 02, 2020
by
Kateřina Sloupová
Browse files
DFA equivalence and emptiness; NFA can epsilon
parent
e7408f3b
Pipeline
#54301
passed with stage
in 25 seconds
Changes
5
Pipelines
1
Expand all
Hide whitespace changes
Inline
Side-by-side
demo.py
View file @
ed41bb02
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
objects.py
0 → 100644
View file @
ed41bb02
This diff is collapsed.
Click to expand it.
parser.py
View file @
ed41bb02
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
...
...
reg_automata.py
View file @
ed41bb02
...
...
@@ -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
:
mov
e_1
=
dfa_1
.
transition
[
state_1
,
character
]
mov
e_2
=
dfa_2
.
transition
[
state_2
,
character
]
mov
e
=
State
(
mov
e_1
.
name
+
"_"
+
mov
e_2
.
name
)
transition
[
state
,
character
]
=
mov
e
dest_stat
e_1
=
dfa_1
.
transition
[
state_1
,
character
]
dest_stat
e_2
=
dfa_2
.
transition
[
state_2
,
character
]
dest_stat
e
=
State
(
dest_stat
e_1
.
name
+
"_"
+
dest_stat
e_2
.
name
)
transition
[
state
,
character
]
=
dest_stat
e
# 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
:
o
riginally
=
dfa
.
transition
[
state
,
character
]
now
=
State
(
"new_"
+
str
(
actual
[
o
riginally
]))
o
ld
=
dfa
.
transition
[
state
,
character
]
now
=
State
(
"new_"
+
str
(
actual
[
o
ld
]))
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
:
mov
e
=
self
.
transition
[
state
,
character
]
dest_stat
e
=
self
.
transition
[
state
,
character
]
if
state
==
self
.
init
:
nonterminal2
=
Nonterminal
(
mov
e
.
name
)
nonterminal2
=
Nonterminal
(
dest_stat
e
.
name
)
terminal
=
Terminal
(
character
.
name
)
if
init
in
rules
:
rules
[
init
].
add
((
terminal
,
nonterminal2
))
else
:
rules
[
init
]
=
{(
terminal
,
nonterminal2
)}
if
mov
e
in
self
.
final
:
if
dest_stat
e
in
self
.
final
:
rules
[
init
].
add
(
terminal
)
else
:
nonterminal1
=
Nonterminal
(
state
.
name
)
nonterminal2
=
Nonterminal
(
mov
e
.
name
)
nonterminal2
=
Nonterminal
(
dest_stat
e
.
name
)
terminal
=
Terminal
(
character
.
name
)
if
nonterminal1
in
rules
:
rules
[
nonterminal1
].
add
((
terminal
,
nonterminal2
))
else
:
rules
[
nonterminal1
]
=
{(
terminal
,
nonterminal2
)}
if
mov
e
in
self
.
final
:
if
dest_stat
e
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
reg_grammars.py
View file @
ed41bb02
...
...
@@ -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
...
...
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment