package project.nfa; import java.util.*; import java.util.Map.Entry; /** * Utilities for analyzing and converting NFA's. * * @author toriscope */ public class NFAUtil { private NFAUtil() { } public static NFA convertToDFA(final NFA nfaInit) { List<MetaState> remainingMetaStates = new LinkedList<MetaState>(); MetaState initialClosure = new MetaState(findClosure(nfaInit.getStartState())); remainingMetaStates.add(initialClosure); List<MetaState> foundMetaStates = new LinkedList<MetaState>(); while (!remainingMetaStates.isEmpty()) { // The meta state for us to analyze MetaState metaState = remainingMetaStates.get(0); foundMetaStates.add(metaState); remainingMetaStates.remove(metaState); // Temporary map of transitions to their following e-enclosures HashMap<String, List<State>> transTo = new HashMap<String, List<State>>(); // Look at each state/transistion for (State state : metaState.states) { for (Transition trans : state.getTransitions()) { // For this trans, place the state in the the transMap if (!trans.isEmptyTransition()) { if (!transTo.containsKey(trans.getString())) { transTo.put(trans.getString(), new LinkedList<State>()); } transTo.get(trans.getString()).addAll(findClosure(trans.getDestinationState())); } } } /* * Build the appropriate meta-states * * For every meta-state this state links to, perhaps add it to the * lists. */ for (Entry<String, List<State>> goToState : transTo.entrySet()) { // Our dummy state, if needed. MetaState possibleState = new MetaState(goToState.getValue()); // If the desired meta-state is in the list, link to it. if (foundMetaStates.contains(possibleState)) { metaState.addTransition(goToState.getKey(), foundMetaStates.get(foundMetaStates.indexOf(possibleState))); } else if (remainingMetaStates.contains(possibleState)) { metaState.addTransition(goToState.getKey(), remainingMetaStates.get(remainingMetaStates.indexOf(possibleState))); } // Otherwise, create a new one and link to it! else { metaState.addTransition(goToState.getKey(), possibleState); remainingMetaStates.add(possibleState); } } } // Build the actual states HashMap<MetaState, State> states = new HashMap<MetaState, State>(); int iName = 0; // Pass one, build state map for (MetaState metaState : foundMetaStates) { // Is goal? boolean isGoal = false; String name = null; for (State s : metaState.states) { if (s.isFinal()) { isGoal = true; name = s.getName(); } } // Build The string of names if (name == null) { name = "" + iName++; } if (metaState == initialClosure) { name = "S"; } states.put(metaState, new State(name, isGoal)); } // Pass two, connect states for (MetaState metaState : foundMetaStates) { for (Entry<String, MetaState> targetState : metaState.transitionTo.entrySet()) { String transToken = targetState.getKey(); boolean matchAll = false; if (matchAll = transToken.isEmpty()) { transToken = "john madden"; } Transition t = new Transition(transToken, states.get(targetState.getValue())); if (matchAll) { t.setMatchAll(); } states.get(metaState).addTransition(t); } } return new NFA(states.get(initialClosure)); } /** * Returns all states within E^* of given state. * * @param state state to find full closure of. * @return all states that can be reached in zero or more E-Closures from * given state. */ public static List<State> findClosure(final State state) { final HashSet<State> explored = new HashSet<State>(); final HashSet<State> frontier = new HashSet<State>(); frontier.add(state); while (!frontier.isEmpty()) { State c = frontier.iterator().next(); for (Transition t : c.getTransitions()) { if (t.isEmptyTransition() && !frontier.contains(t.getDestinationState()) && !explored.contains(t.getDestinationState())) { frontier.add(t.getDestinationState()); } } frontier.remove(c); explored.add(c); } return new LinkedList<State>(explored); } public static List<State> getAllReachableStates(final State state) { final HashSet<State> explored = new HashSet<State>(); final HashSet<State> frontier = new HashSet<State>(); frontier.add(state); while (!frontier.isEmpty()) { State c = frontier.iterator().next(); for (Transition t : c.getTransitions()) { if (!frontier.contains(t.getDestinationState()) && !explored.contains(t.getDestinationState())) { frontier.add(t.getDestinationState()); } } frontier.remove(c); explored.add(c); } return new LinkedList<State>(explored); } /** * Minimize a given DFA. This will effect the given DFA. * * @param dfa the dfa you want to minimize. */ public static void minimizeDFA(NFA dfa) { if (!dfa.isDFA()) { throw new RuntimeException("Must be DFA"); } // Standard algorithm using table List<State> states = new LinkedList<State>(getAllReachableStates(dfa.getStartState())); HashMap<Set<State>, Character> tranTable = new HashMap<Set<State>, Character>(); // Fill taken-up unions for (int j = 0; j < states.size(); j++) { for (int i = 0; i < states.size(); i++) { if (j <= i) continue; Set<State> tuple = set(states.get(i), states.get(j)); boolean a = states.get(i).isFinal(); boolean b = states.get(j).isFinal(); if ((a || b) && !(a && b)) { tranTable.put(tuple, 'e'); } else { tranTable.put(tuple, null); } } } boolean changeOccurred; do { // No changes found yet changeOccurred = false; //Comb the table, looking for unmarked unions for (int j = 0; j < states.size(); j++) { for (int i = 0; i < states.size(); i++) { if (j <= i) continue; Set<State> tuple = set(states.get(i), states.get(j)); // Found one that must be looked at if (tranTable.get(tuple) == null) { //gather symbols Set<String> trans = new HashSet<String>(); for (State state : tuple) { for (Transition transition : state.getNonEmptyTransitions()) { trans.add(transition.getString()); } } //for each symbol, check if states can be combined for (String symbol : trans) { Transition a = states.get(i).getTransByString(symbol); Transition b = states.get(j).getTransByString(symbol); // If both transitions exist if (a != null && b != null) { //if trans on both states, by symbol, is not blank if (tranTable.get(set(a.getDestinationState(), b.getDestinationState())) != null) { //distinct(bothstates) = symbol tranTable.put(tuple, 's'); changeOccurred = true; } } } } } } } while (changeOccurred); //Build new combined-states HashMap<State, State> oldToNew = new HashMap<State, State>(); for (Entry<Set<State>, Character> entry : tranTable.entrySet()) { // Found combine-able states if (entry.getValue() == null) { List<State> twoStates = new ArrayList<State>(entry.getKey()); State a = twoStates.get(0); State b = twoStates.get(1); // a in map if (oldToNew.containsKey(a)) { oldToNew.put(b, oldToNew.get(a)); } // b in map else if (oldToNew.containsKey(b)) { oldToNew.put(a, oldToNew.get(b)); } // none in map else { State state = new State(a.isFinal() ? a.getName() : (a.getName() + ", " + b.getName()), a.isFinal()); oldToNew.put(a, state); oldToNew.put(b, state); } } } // move all transitions for (State s : states) { for (Transition transition : s.getTransitions()) { if (oldToNew.containsKey(transition.getDestinationState())) { transition.setDestinationState(oldToNew.get(transition.getDestinationState())); } } } // Move the transitions to the new comb states for (Entry<State, State> entry : oldToNew.entrySet()) { entry.getValue().addTransition(entry.getKey().getTransitions().toArray(new Transition[0])); } // if the start was combined, make sure to reattach it if (oldToNew.containsKey(dfa.getStartState())) { dfa.setStartState(oldToNew.get(dfa.getStartState())); } } private static <T> Set<T> set(T... stuff) { return new HashSet<T>(Arrays.asList(stuff)); } public static boolean isValid(final NFASegment nfa, final String string) { return isValid(new NFA(nfa), string); } /** * Check if the string is valid in the NFA. * * @param nfa the nfa to check the string against * @param string string to check for validity * @return true if the string is valid, false otherwise. */ public static boolean isValid(final NFA nfa, final String string) { return isValidVerbose(nfa, string).isValid; } public static class WalkResult { public boolean isValid = false; int transitions = -1; public String toString() { return "Valid: " + isValid + "\nTransitions Taken: " + transitions; } } public static WalkResult isValidVerbose(final NFA nfa, final String string) { WalkResult result = new WalkResult(); final HashSet<NFAStep> steps = new HashSet<NFAStep>(); final HashSet<NFAStep> explored = new HashSet<NFAStep>(); boolean isValid = false; steps.add(new NFAStep(nfa.getStartState(), string)); while (!(isValid || steps.isEmpty())) { NFAStep step = steps.iterator().next(); explored.add(step); steps.remove(step); result.transitions++; // Check for finality if (step.isFinal()) { isValid = true; } for (Transition t : step.state.getTransitions()) { NFAStep s = null; // If empty if (t.isEmptyTransition()) { s = new NFAStep(t.getDestinationState(), step.string); } // if not empty, and string is not empty else if (!step.string.isEmpty() && t.isValid("" + step.string.charAt(0))) { s = new NFAStep(t.getDestinationState(), step.string.substring(1)); } if (s != null && !explored.contains(s)) { steps.add(s); } } } result.isValid = isValid; return result; } /** * Execution step in an NFA walk. * * @author toriscope */ private static class NFAStep { private final State state; private final String string; public NFAStep(final State state, final String string) { this.state = state; this.string = string; } @Override public boolean equals(Object o) { if (o instanceof NFAStep) { NFAStep p = (NFAStep) o; return p.state == this.state && p.string.equals(this.string); } else { System.err.println("Object is not an instance of NFAStep"); return false; } } @Override public int hashCode() { return state.hashCode() * string.hashCode(); } public String toString() { return state.toString() + " " + string; } public boolean isFinal() { return string.isEmpty() && state.isFinal(); } } /** * Single meta-state entry in E-Closure table * * @author toriscope */ private static class MetaState { private final List<State> states; private final HashMap<String, MetaState> transitionTo; public MetaState(List<State> states) { this.states = states; transitionTo = new HashMap<String, MetaState>(); } public void addTransition(String trans, MetaState s) { transitionTo.put(trans, s); } @Override public int hashCode() { int ohgeez = 1; for (State s : states) { ohgeez *= s.hashCode(); } return ohgeez; } @Override public boolean equals(final Object o) { if (o instanceof MetaState) { MetaState s = (MetaState) o; if (s.states.size() != this.states.size()) { return false; } for (State state : s.states) { if (!this.states.contains(state)) { return false; } } } else return false; return true; } } // Create unique names private static int gen = 0; public static NFASegment empty() { State start = new State("start" + gen, false); State end = new State("end" + gen++, false); start.addTransition(new Transition(end)); return new NFASegment(start, end); } public static NFASegment a(final String regex) { State start = new State("start" + gen, false); State end = new State("end" + gen++, false); start.addTransition(new Transition(regex, end)); return new NFASegment(start, end); } public static NFASegment aOrB(final NFASegment... segments) { State start = new State("start" + gen, false); State end = new State("end" + gen++, false); if (segments.length < 1) { start.addTransition(new Transition(end)); } for (NFASegment s : segments) { start.addTransition(new Transition(s.start)); s.end.addTransition(new Transition(end)); } return new NFASegment(start, end); } public static NFASegment aStar(final NFASegment a) { State start = new State("start" + gen, false); State end = new State("end" + gen++, false); start.addTransition(new Transition(a.start)); start.addTransition(new Transition(end)); a.end.addTransition(new Transition(end)); end.addTransition(new Transition(start)); return new NFASegment(start, end); } public static NFASegment aPlus(final NFASegment a) { State start = new State("start" + gen, false); State end = new State("end" + gen++, false); start.addTransition(new Transition(a.start)); a.end.addTransition(new Transition(end)); end.addTransition(new Transition(start)); return new NFASegment(start, end); } public static NFASegment ab(final NFASegment a, final NFASegment b) { a.end.addTransition(new Transition(b.start)); return new NFASegment(a.start, b.end); } public static NFASegment dot() { State start = new State("start" + gen, false); State end = new State("end" + gen++, false); start.addTransition(Transition.createDotTransition(end)); return new NFASegment(start, end); } public static class NFASegment { public final State start, end; public NFASegment(final State start, final State end) { this.start = start; this.end = end; } } }