/* This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.opentripplanner.routing.automata; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Queue; import java.util.Set; public class NFA { /** the nonterminal which this state machine accepts */ public final Nonterminal nt; public final Set<AutomatonState> startStates = new HashSet<AutomatonState>(); public final Set<AutomatonState> acceptStates = new HashSet<AutomatonState>(); /** for DFAs, state list indexes are same as transition table indexes. */ final List<AutomatonState> states = new ArrayList<AutomatonState>(); public NFA(Nonterminal nt) { this(nt, true); } protected NFA(Nonterminal nt, boolean build) { this.nt = nt; if (build) { AutomatonState start = new AutomatonState("START"); AutomatonState accept = nt.build(start); this.startStates.add(start); this.acceptStates.add(accept); this.states.addAll(findStates()); this.relabelNodes(); } } public DFA toDFA() { return new DFA(this); } /** Do a Dijkstra search from the start state to find all reachable states in this NFA. */ private Set<AutomatonState> findStates() { Set<AutomatonState> states = new HashSet<AutomatonState>(); Queue<AutomatonState> q = new LinkedList<AutomatonState>(); q.addAll(startStates); states.addAll(startStates); while (!q.isEmpty()) { AutomatonState s = q.poll(); Set<AutomatonState> targets = new HashSet<AutomatonState>(); for (Transition transition : s.transitions) targets.add(transition.target); targets.addAll(s.epsilonTransitions); for (AutomatonState target : targets) if (states.add(target)) q.add(target); } return states; } public NFA reverse() { Set<AutomatonState> states = this.findStates(); Map<AutomatonState, AutomatonState> newStates = new HashMap<AutomatonState, AutomatonState>(); for (AutomatonState state : states) newStates.put(state, new AutomatonState(state.label)); for (AutomatonState oldFromState : states) { AutomatonState newToState = newStates.get(oldFromState); for (Transition t : oldFromState.transitions) { AutomatonState newFromState = newStates.get(t.target); newFromState.transitions.add(new Transition(t.terminal, newToState)); } for (AutomatonState oldToState : oldFromState.epsilonTransitions) { AutomatonState newFromState = newStates.get(oldToState); newFromState.epsilonTransitions.add(newToState); } } NFA result = new NFA(this.nt, false); for (AutomatonState old : this.acceptStates) result.startStates.add(newStates.get(old)); for (AutomatonState old : this.startStates) result.acceptStates.add(newStates.get(old)); return result; } /** * Convert to a minimal DFA using Brzozowski's algorithm: reverse the transitions and apply the * powerset construction, yielding a minimal DFA for the reverse language, then repeat the * procedure to get a minimal DFA for the original language. */ public DFA minimize() { return this.reverse().toDFA().reverse().toDFA(); } public String toGraphViz() { /* first, find all states reachable from the start state */ Set<AutomatonState> states = this.findStates(); /* build DOT file node styles */ StringBuilder sb = new StringBuilder(); sb.append("digraph automaton { \n"); sb.append(" rankdir=LR; \n"); sb.append(" node [shape = doublecircle]; \n"); for (AutomatonState as : acceptStates) sb.append(String.format(" %s; \n", as.label)); sb.append(" node [shape = circle]; \n"); /* make edges for terminal and epsilon transitions */ for (AutomatonState fromState : states) { for (Transition transition : fromState.transitions) { sb.append(" " + fromState.label); sb.append(" -> "); sb.append(transition.target.label); String label = Integer.toString(transition.terminal); sb.append(String.format(" [label=%s];\n", label)); } for (AutomatonState toState : fromState.epsilonTransitions) { sb.append(" " + fromState.label); sb.append(" -> "); sb.append(toState.label); sb.append(" [label=ε];\n"); } } sb.append("}\n"); return sb.toString(); } protected void relabelNodes() { char c = 'A'; for (AutomatonState state : this.states) { if (state.label == null) { state.label = Character.toString(c); c += 1; if (c > 'Z') c = 'A'; } } } }