|
17 | 17 | Iterable,
|
18 | 18 | Iterator,
|
19 | 19 | List,
|
20 |
| - Literal, |
21 | 20 | Mapping,
|
22 | 21 | Optional,
|
23 | 22 | Set,
|
24 | 23 | Tuple,
|
25 | 24 | Type,
|
26 |
| - TypeVar, |
27 | 25 | cast,
|
28 | 26 | )
|
29 | 27 |
|
30 | 28 | import networkx as nx
|
31 |
| -from pydot import Dot, Edge, Node |
32 | 29 | from typing_extensions import Self
|
33 | 30 |
|
34 | 31 | import automata.base.exceptions as exceptions
|
|
50 | 47 | class DFA(fa.FA):
|
51 | 48 | """A deterministic finite automaton."""
|
52 | 49 |
|
53 |
| - # TODO allow |
54 | 50 | __slots__ = (
|
55 | 51 | "states",
|
56 | 52 | "input_symbols",
|
@@ -332,7 +328,7 @@ def minify(self, retain_names: bool = False) -> Self:
|
332 | 328 | Create a minimal DFA which accepts the same inputs as this DFA.
|
333 | 329 |
|
334 | 330 | 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. |
336 | 332 | retain_names: If True, merged states retain names.
|
337 | 333 | If False, new states will be named 0, ..., n-1.
|
338 | 334 | """
|
@@ -807,7 +803,7 @@ def predecessors(
|
807 | 803 | *,
|
808 | 804 | strict: bool = True,
|
809 | 805 | key: Optional[Callable[[Any], Any]] = None,
|
810 |
| - ) -> Iterable[str]: |
| 806 | + ) -> Generator[str, None, None]: |
811 | 807 | """
|
812 | 808 | Generates all strings that come before the input string
|
813 | 809 | in lexicographical order.
|
@@ -845,7 +841,7 @@ def successors(
|
845 | 841 | strict: bool = True,
|
846 | 842 | key: Optional[Callable[[Any], Any]] = None,
|
847 | 843 | reverse: bool = False,
|
848 |
| - ) -> Iterable[str]: |
| 844 | + ) -> Generator[str, None, None]: |
849 | 845 | """
|
850 | 846 | Generates all strings that come after the input string in
|
851 | 847 | lexicographical order. Passing in None will generate all words. If
|
@@ -906,7 +902,7 @@ def successors(
|
906 | 902 | )
|
907 | 903 | # Traverse to child if candidate is viable
|
908 | 904 | if candidate_state in coaccessible_nodes:
|
909 |
| - state_stack.append(cast(str, candidate_state)) |
| 905 | + state_stack.append(candidate_state) |
910 | 906 | char_stack.append(cast(str, candidate))
|
911 | 907 | candidate = first_symbol
|
912 | 908 | else:
|
@@ -1553,38 +1549,35 @@ def get_name_original(states: FrozenSet[DFAStateT]) -> DFAStateT:
|
1553 | 1549 | final_states=dfa_final_states,
|
1554 | 1550 | )
|
1555 | 1551 |
|
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]: |
1557 | 1564 | """
|
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 | +
|
1559 | 1575 | """
|
1560 |
| - # Nodes are set of states |
1561 | 1576 |
|
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