Skip to content

Commit d90100a

Browse files
khoda81caleb531eliotwrobson
authored
Added jupyter notebook integration and new visualization (#129)
Co-authored-by: Caleb Evans <[email protected]> Co-authored-by: Eliot Robson <[email protected]>
1 parent b36d726 commit d90100a

File tree

16 files changed

+719
-297
lines changed

16 files changed

+719
-297
lines changed

.coveragerc

Lines changed: 0 additions & 23 deletions
This file was deleted.

.github/workflows/lint.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ jobs:
2323
with:
2424
python-version: "3.11"
2525

26-
- name: Install graphviz
27-
run: sudo apt-get install graphviz
26+
- name: Setup Graphviz
27+
uses: ts-graphviz/setup-graphviz@v1
2828

2929
- name: Install dependencies
3030
run: |

.github/workflows/tests.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ jobs:
2727
with:
2828
python-version: ${{ matrix.python-version }}
2929

30-
- name: Install graphviz
31-
run: sudo apt-get install graphviz
30+
- name: Setup Graphviz
31+
uses: ts-graphviz/setup-graphviz@v1
3232

3333
- name: Install dependencies
3434
run: |

automata/fa/dfa.py

Lines changed: 33 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,15 @@
1717
Iterable,
1818
Iterator,
1919
List,
20-
Literal,
2120
Mapping,
2221
Optional,
2322
Set,
2423
Tuple,
2524
Type,
26-
TypeVar,
2725
cast,
2826
)
2927

3028
import networkx as nx
31-
from pydot import Dot, Edge, Node
3229
from typing_extensions import Self
3330

3431
import automata.base.exceptions as exceptions
@@ -50,7 +47,6 @@
5047
class DFA(fa.FA):
5148
"""A deterministic finite automaton."""
5249

53-
# TODO allow
5450
__slots__ = (
5551
"states",
5652
"input_symbols",
@@ -332,7 +328,7 @@ def minify(self, retain_names: bool = False) -> Self:
332328
Create a minimal DFA which accepts the same inputs as this DFA.
333329
334330
First, non-reachable states are removed.
335-
Then, similiar states are merged using Hopcroft's Algorithm.
331+
Then, similar states are merged using Hopcroft's Algorithm.
336332
retain_names: If True, merged states retain names.
337333
If False, new states will be named 0, ..., n-1.
338334
"""
@@ -807,7 +803,7 @@ def predecessors(
807803
*,
808804
strict: bool = True,
809805
key: Optional[Callable[[Any], Any]] = None,
810-
) -> Iterable[str]:
806+
) -> Generator[str, None, None]:
811807
"""
812808
Generates all strings that come before the input string
813809
in lexicographical order.
@@ -845,7 +841,7 @@ def successors(
845841
strict: bool = True,
846842
key: Optional[Callable[[Any], Any]] = None,
847843
reverse: bool = False,
848-
) -> Iterable[str]:
844+
) -> Generator[str, None, None]:
849845
"""
850846
Generates all strings that come after the input string in
851847
lexicographical order. Passing in None will generate all words. If
@@ -906,7 +902,7 @@ def successors(
906902
)
907903
# Traverse to child if candidate is viable
908904
if candidate_state in coaccessible_nodes:
909-
state_stack.append(cast(str, candidate_state))
905+
state_stack.append(candidate_state)
910906
char_stack.append(cast(str, candidate))
911907
candidate = first_symbol
912908
else:
@@ -1553,38 +1549,35 @@ def get_name_original(states: FrozenSet[DFAStateT]) -> DFAStateT:
15531549
final_states=dfa_final_states,
15541550
)
15551551

1556-
def show_diagram(self, path=None):
1552+
def iter_transitions(
1553+
self,
1554+
) -> Generator[Tuple[DFAStateT, DFAStateT, str], None, None]:
1555+
return (
1556+
(from_, to_, symbol)
1557+
for from_, lookup in self.transitions.items()
1558+
for symbol, to_ in lookup.items()
1559+
)
1560+
1561+
def _get_input_path(
1562+
self, input_str
1563+
) -> Tuple[List[Tuple[DFAStateT, DFAStateT, DFASymbolT]], bool]:
15571564
"""
1558-
Creates the graph associated with this DFA
1565+
Calculate the path taken by input.
1566+
1567+
Args:
1568+
input_str (str): The input string to run on the DFA.
1569+
1570+
Returns:
1571+
tuple[list[tuple[DFAStateT, DFAStateT, DFASymbolT], bool]]: A list
1572+
of all transitions taken in each step and a boolean indicating
1573+
whether the DFA accepted the input.
1574+
15591575
"""
1560-
# Nodes are set of states
15611576

1562-
graph = Dot(graph_type="digraph", rankdir="LR")
1563-
nodes = {}
1564-
for state in self.states:
1565-
if state == self.initial_state:
1566-
# color start state with green
1567-
if state in self.final_states:
1568-
initial_state_node = Node(
1569-
state, style="filled", peripheries=2, fillcolor="#66cc33"
1570-
)
1571-
else:
1572-
initial_state_node = Node(
1573-
state, style="filled", fillcolor="#66cc33"
1574-
)
1575-
nodes[state] = initial_state_node
1576-
graph.add_node(initial_state_node)
1577-
else:
1578-
if state in self.final_states:
1579-
state_node = Node(state, peripheries=2)
1580-
else:
1581-
state_node = Node(state)
1582-
nodes[state] = state_node
1583-
graph.add_node(state_node)
1584-
# adding edges
1585-
for from_state, lookup in self.transitions.items():
1586-
for to_label, to_state in lookup.items():
1587-
graph.add_edge(Edge(nodes[from_state], nodes[to_state], label=to_label))
1588-
if path:
1589-
graph.write_png(path)
1590-
return graph
1577+
state_history = list(self.read_input_stepwise(input_str, ignore_rejection=True))
1578+
path = list(zip(state_history, state_history[1:], input_str))
1579+
1580+
last_state = state_history[-1] if state_history else self.initial_state
1581+
accepted = last_state in self.final_states
1582+
1583+
return path, accepted

0 commit comments

Comments
 (0)