/*
* JFLAP - Formal Languages and Automata Package
*
*
* Susan H. Rodger
* Computer Science Department
* Duke University
* August 27, 2009
* Copyright (c) 2002-2009
* All rights reserved.
* JFLAP is open source software. Please see the LICENSE for terms.
*
*/
package automata.fsa;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Iterator;
import java.util.StringTokenizer;
import automata.AlphabetRetriever;
import automata.Automaton;
import automata.AutomatonChecker;
import automata.ClosureTaker;
import automata.State;
import automata.StatePlacer;
import automata.Transition;
/**
* The NFA to DFA converter object can be used to convert a nondeterministic
* finite state automaton to a deterministic finite state automaton. To use the
* converter, you first need to get the initial state for the dfa you are
* building by calling createInitialState. Then you simply continue to expand
* the states in the dfa by calling expandState until you have no more states in
* your dfa that you haven't expanded. See convertToDFA for help on how to
* perform the conversion. (WARNING: You will want to clone the NFA before you
* call convertToDFA because it will change the automaton.) This nfa to dfa
* conversion requires that the labels on the states in the dfa list the states
* from the nfa that they represent because it is from the label that the
* converter determines which states from the nfa a state in the dfa represents.
* There is no map used here to allow for the user to create and label states
* himself, without having to worry about mapping the state to the states it
* represents.
*
* @author Ryan Cavalcante
*/
public class NFAToDFA {
/**
* Creates an instance of NFAToDFA
*/
public NFAToDFA() {
}
/**
* Returns the initial state for dfa
. A state is created to
* represent the initial state from nfa
(and its closure),
* and added to dfa
. The initial state of dfa
* is set to the returned state.
*
* @param nfa
* the nfa being converted to a dfa
* @param dfa
* the dfa being built during the conversion.
*/
public State createInitialState(Automaton nfa, Automaton dfa) {
/** get closure of initial state from nfa. */
State initialState = nfa.getInitialState();
State[] initialClosure = ClosureTaker.getClosure(initialState, nfa);
/**
* create state in dfa to represent closure of initial state in nfa.
*/
State state = createStateWithStates(dfa, initialClosure, nfa);
// StatePlacer sp = new StatePlacer();
// Point point = sp.getPointForState(dfa);
// State state = dfa.createState(point);
/** set to initial state in dfa. */
dfa.setInitialState(state);
// state.setLabel(getStringForStates(initialClosure));
// if(hasFinalState(initialClosure, nfa)) {
// dfa.addFinalState(state);
// }
return state;
}
/**
* Returns true if one or more of the states in states
are
* final.
*
* @param states
* the set of states
* @param automaton
* the automaton that contains states
* @return true if one or more of the states in states
are
* final
*/
public boolean hasFinalState(State[] states, Automaton automaton) {
for (int k = 0; k < states.length; k++) {
if (automaton.isFinalState(states[k]))
return true;
}
return false;
}
/**
* Returns the State array mapped to state
.
*
* @param state
* the state.
* @param automaton
* the nfa whose states are represented by state
.
* @return the State array mapped to state
.
*/
public State[] getStatesForState(State state, Automaton automaton) {
if (state.getLabel() == null)
return new State[0];
StringTokenizer tokenizer = new StringTokenizer(state.getLabel(),
" \t\n\r\f,q");
ArrayList states = new ArrayList();
while (tokenizer.hasMoreTokens())
states.add(automaton.getStateWithID(Integer.parseInt(tokenizer
.nextToken())));
return (State[]) states.toArray(new State[0]);
}
/**
* Returns a string representation of states
.
*
* @param states
* the set of states.
* @return a string representation of states
.
*/
public String getStringForStates(State[] states) {
StringBuffer buffer = new StringBuffer();
for (int k = 0; k < states.length - 1; k++) {
buffer.append(Integer.toString(states[k].getID()));
buffer.append(",");
}
buffer.append(Integer.toString(states[states.length - 1].getID()));
return buffer.toString();
}
/**
* Returns all states reachable on terminal
from states
,
* including the closure of all reachable states.
*
* @param terminal
* the terminal (alphabet character)
* @param states
* the set of states that we are checking to see if they have
* transitions on terminal
.
* @param automaton
* the automaton.
* @return all states reachable on terminal
from states
,
* including the closure of all reachable states.
*/
public State[] getStatesOnTerminal(String terminal, State[] states,
Automaton automaton) {
ArrayList list = new ArrayList();
for (int k = 0; k < states.length; k++) {
State state = states[k];
Transition[] transitions = automaton.getTransitionsFromState(state);
for (int i = 0; i < transitions.length; i++) {
FSATransition transition = (FSATransition) transitions[i];
if (transition.getLabel().equals(terminal)) {
State toState = transition.getToState();
State[] closure = ClosureTaker.getClosure(toState,
automaton);
for (int j = 0; j < closure.length; j++) {
if (!list.contains(closure[j])) {
list.add(closure[j]);
}
}
}
}
}
return (State[]) list.toArray(new State[0]);
}
/**
* Returns true if states
contains state
*
* @param state
* the state.
* @param states
* the states.
* @return true if states
contains state
*/
private boolean containsState(State state, State[] states) {
for (int k = 0; k < states.length; k++) {
if (states[k] == state)
return true;
}
return false;
}
/**
* Returns true if states1
and states2
are
* identical (i.e. they contain exactly the same states, and no extras).
*
* @param states1
* a set of states
* @param states2
* a set of states
* @return true if states1
and states2
are
* identical (i.e. they contain exactly the same states, and no
* extras).
*/
public boolean containSameStates(State[] states1, State[] states2) {
int len1 = states1.length;
int len2 = states2.length;
if (len1 != len2)
return false;
Arrays.sort(states1, new Comparator(){
public int compare(State s, State t){
return s.hashCode() - t.hashCode();
}
});
Arrays.sort(states2, new Comparator(){
public int compare(State s, State t){
return s.hashCode() - t.hashCode();
}
});
for (int k = 0; k < states1.length; k++) {
// if (!containsState(states1[k], states2))
// return false;
if (states1[k] != states2[k])
return false;
}
return true;
}
/**
* Returns the State mapped to states
.
*
* @param states
* the states
* @param dfa
* the automaton that contains a state that is mapped to states
* @return the State mapped to states
.
*/
public State getStateForStates(State[] states, Automaton dfa, Automaton nfa) {
State[] dfaStates = dfa.getStates();
for (int k = 0; k < dfaStates.length; k++) {
State[] nfaStates = getStatesForState(dfaStates[k], nfa);
if (containSameStates(nfaStates, states)) {
return dfaStates[k];
}
}
return null;
}
/**
* Returns a list of States created by expanding state
in
* dfa
. state is a state in dfa
* that represents a set of states in nfa
. This method adds
* transitions to dfa
from state
on all
* terminals in the alphabet of nfa
for which it is relevant.
* For each letter in the alphabet, you determine the reachable states (from
* nfa
) from the set of states represented by state
.
* You then create a state in dfa
that represents all these
* reachable states and add a transition to DFA connecting state
* and this newly created state.
*
* @param state
* the state from dfa
* @param nfa
* the nfa being converted to a dfa
* @param dfa
* the dfa being built from the conversion
* @return a list of States created by expanding state
.
*/
public ArrayList expandState(State state, Automaton nfa, Automaton dfa) {
ArrayList list = new ArrayList();
AlphabetRetriever far = new FSAAlphabetRetriever();
String[] alphabet = far.getAlphabet(nfa);
/** for each letter in the alphabet. */
for (int k = 0; k < alphabet.length; k++) {
/**
* get states reachable on terminal from all states represented by
* state.
*/
State[] states = getStatesOnTerminal(alphabet[k],
getStatesForState(state, nfa), nfa);
/** if any reachable states on terminal. */
if (states.length > 0) {
/**
* get state from dfa that represents list of reachable states
* in nfa.
*/
State toState = getStateForStates(states, dfa, nfa);
/** if no such state. */
if (toState == null) {
/** create state, add to list */
toState = createStateWithStates(dfa, states, nfa);
// StatePlacer sp = new StatePlacer();
// Point point = sp.getPointForState(dfa);
// toState = dfa.createState(point);
// toState.setLabel(getStringForStates(states));
// if(hasFinalState(states, nfa)) {
// dfa.addFinalState(toState);
// }
list.add(toState);
}
/**
* add transition to dfa from state to state that represents
* reachables states on terminal from nfa.
*/
Transition transition = new FSATransition(state, toState,
alphabet[k]);
dfa.addTransition(transition);
}
}
return list;
}
/**
* Creates a state in dfa
, labelled with the set of states
* in states
, which are all states from nfa
.
*
* @param dfa
* the dfa. the automaton the state is added to
* @param states
* the set of states from the nfa that this created state in the
* dfa will represent
* @param nfa
* the nfa
* @return the created state
*/
public State createStateWithStates(Automaton dfa, State[] states,
Automaton nfa) {
StatePlacer sp = new StatePlacer();
State state = dfa.createState(sp.getPointForState(dfa));
state.setLabel(getStringForStates(states));
if (hasFinalState(states, nfa)) {
dfa.addFinalState(state);
}
return state;
}
/**
* Returns a deterministic finite state automaton equivalent to automaton
.
* automaton
is not at all affected by this conversion.
*
* @param automaton
* the automaton to convert to a dfa.
* @return a deterministic finite state automaton equivalent to automaton
.
*/
public FiniteStateAutomaton convertToDFA(Automaton automaton) {
/** check if actually nfa. */
AutomatonChecker ac = new AutomatonChecker();
if (!ac.isNFA(automaton)) {
return (FiniteStateAutomaton) automaton.clone();
}
/** remove multiple character labels. */
if (FSALabelHandler.hasMultipleCharacterLabels(automaton)) {
FSALabelHandler.removeMultipleCharacterLabelsFromAutomaton(automaton);
}
/** create new finite state automaton. */
FiniteStateAutomaton dfa = new FiniteStateAutomaton();
State initialState = createInitialState(automaton, dfa);
/**
* get initial state and add to list of states that need to be expanded.
*/
ArrayList list = new ArrayList();
list.add(initialState);
/** while still more states to be expanded. */
while (!list.isEmpty()) {
ArrayList statesToExpand = new ArrayList();
Iterator it = list.iterator();
while (it.hasNext()) {
State state = (State) it.next();
/** expand state. */
statesToExpand.addAll(expandState(state, automaton, dfa));
it.remove();
}
list.addAll(statesToExpand);
}
return dfa;
}
}