/*
* 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.awt.Point;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import javax.swing.tree.DefaultTreeModel;
import automata.AlphabetRetriever;
import automata.Automaton;
import automata.AutomatonChecker;
import automata.State;
import automata.StatePlacer;
import automata.Transition;
import automata.UnreachableStatesDetector;
/**
* The Minimizer object can be used to minimize a deterministic finite state
* automaton. In order to use the Minimizer, you must call initializeMinimizer
* before each minimization. Then you must get a compatible automaton (i.e. one
* with no reachable states, and an added trap state if necessary) by calling
* getMinimizeableAutomaton. You can then immediately get the tree of
* distinugishable groups by calling getDistinguishableGroupsTree, or you can
* build the tree yourself step by step by continually getting another
* distinguishable group (i.e a group that can still be further split) by
* calling getDistinguishableGroup. Then you call split on this group and it
* will return the groups obtained from the split of the distinguishable group.
* Or instead of calling split (since you will want to know what terminal in the
* alphabet you can split the group on), you can call getTerminalToSplit on the
* group and then call splitOnTerminal on that same group (this is essentially
* what split does anyway). Or if the user tries to split a node, you can call
* isSplittable to see if the group can be split further. Or if the user tries
* to split a group on a particular terminal, you can call
* isSplittableOnTerminal to see if the group can be split on the terminal. When
* you call splitOnTerminal, it will return a list of new groups created from
* splitting the old group. You can add these groups to your tree under the
* distinguishable group. If you continue to call getDistinguishableGroup until
* it returns null (or until a call to isMinimized returns false), you will have
* split the groups as far as they can go. At this point, you should have a
* complete tree of distinguishable groups. You can then use this tree to get
* the minimum dfa by calling getMinimumDfa. Or you can build the dfa step by
* step from the tree by first calling createStatesForMinimumDfa, which will
* create a state for each of the distinguishable groups. Then you can expand
* each of these states one by one by calling getTransitionsForState, which will
* return a list of all transitions coming from the state. By adding all these
* transitions to the minimum dfa, you will have successfully created the
* minimum dfa.
*
* @author Ryan Cavalcante
*/
public class Minimizer {
/**
* Creates an instance of Minimizer
.
*/
public Minimizer() {
}
/**
* Initializes the minimizer to begin a new minimization.
*/
public void initializeMinimizer() {
MAP = new HashMap();
// DISTINGUISHABLE_GROUPS = new ArrayList();
TRAP_STATE = null;
}
/**
* Returns a string representation of states
*
* @param states
* the array of State objects.
* @return a string representation of states
*/
public String getString(State[] states) {
if (states.length == 0)
return "";
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 the terminal that group
can be split on.
*
* @param group
* the group being split
* @param automaton
* the automaton
* @return the terminal that group
can be split on.
*/
public String getTerminalToSplit(State[] group, Automaton automaton,
DefaultTreeModel tree) {
AlphabetRetriever far = new FSAAlphabetRetriever();
String[] alphabet = far.getAlphabet(automaton);
for (int k = 0; k < alphabet.length; k++) {
if (isSplittableOnTerminal(group, alphabet[k], automaton, tree)) {
return alphabet[k];
}
}
return null;
}
/**
* Returns true if group
is splittable on terminal
.
*
* @param group
* the group to split
* @param terminal
* the terminal to split group on
* @param automaton
* the automaton
* @return true if group
is splittable on terminal
.
*/
public boolean isSplittableOnTerminal(State[] group, String terminal,
Automaton automaton, DefaultTreeModel tree) {
/** if group goes to more than one group on terminal. */
if (getGroupsFromGroupOnTerminal(group, terminal, automaton, tree)
.size() > 1) {
return true;
}
return false;
}
/**
* Returns true if group
can be further split.
*
* @param group
* the group
* @param automaton
* the automaton
* @return true if group
can be further split.
*/
public boolean isSplittable(State[] group, Automaton automaton,
DefaultTreeModel tree) {
/** if only one state in group, can't be split. */
if (group.length <= 1)
return false;
AlphabetRetriever far = new FSAAlphabetRetriever();
String[] alphabet = far.getAlphabet(automaton);
for (int k = 0; k < alphabet.length; k++) {
/** if group splittable on a terminal in alphabet. */
if (isSplittableOnTerminal(group, alphabet[k], automaton, tree)) {
return true;
}
}
return false;
}
/**
* Returns the array of states that represent a distinguishable group (i.e.
* a group that can be further split/distinguished).
*
* @param automaton
* the automaton
* @return the array of States that represent a distinguishable group.
*/
public State[] getDistinguishableGroup(Automaton automaton,
DefaultTreeModel tree) {
MinimizeTreeNode root = (MinimizeTreeNode) tree.getRoot();
ArrayList distinguishableGroups = getLeaves(tree, root);
Iterator it = distinguishableGroups.iterator();
while (it.hasNext()) {
State[] group = (State[]) it.next();
if (isSplittable(group, automaton, tree))
return group;
}
return null;
}
/**
* Returns an array of states that represents a group, of which state
* is a member.
*
* @param state
* the state.
* @return an array of states that represents a group, of which state
* is a member.
*/
public State[] getGroupForState(State state, DefaultTreeModel tree) {
MinimizeTreeNode root = (MinimizeTreeNode) tree.getRoot();
ArrayList distinguishableGroups = getLeaves(tree, root);
Iterator it = distinguishableGroups.iterator();
// Iterator it = DISTINGUISHABLE_GROUPS.iterator();
while (it.hasNext()) {
State[] group = (State[]) it.next();
for (int k = 0; k < group.length; k++) {
if (group[k] == state) {
return group;
}
}
}
return null;
}
/**
* Returns true if state
in automaton
has a
* transition to one of the states in group
on terminal
.
*
* @param state
* the state.
* @param group
* the set of states that state might have a transition to.
* @param terminal
* the terminal
* @param automaton
* the automaton.
* @return true if state
in automaton
has a
* transition to one of the states in group
on terminal
.
*/
public boolean stateGoesToGroupOnTerminal(State state, State[] group,
String terminal, Automaton automaton) {
for (int k = 0; k < group.length; k++) {
Transition[] transitions = automaton
.getTransitionsFromStateToState(state, group[k]);
for (int j = 0; j < transitions.length; j++) {
FSATransition trans = (FSATransition) transitions[j];
String label = trans.getLabel();
if (label.equals(terminal)) {
return true;
}
}
}
return false;
}
/**
* Returns a list of groups (State[]) that group
goes to on
* terminal
.
*
* @param group
* the group
* @param terminal
* the terminal
* @param automaton
* the automaton
* @return a list of groups (State[]) that group
goes to on
* terminal
.
*/
public ArrayList getGroupsFromGroupOnTerminal(State[] group,
String terminal, Automaton automaton, DefaultTreeModel tree) {
ArrayList list = new ArrayList();
for (int k = 0; k < group.length; k++) {
if (group[k].getAutomaton() != automaton)
System.err.println("BADNESS! BADNESS!");
Transition[] transitions = automaton
.getTransitionsFromState(group[k]);
for (int j = 0; j < transitions.length; j++) {
FSATransition trans = (FSATransition) transitions[j];
if (trans.getLabel().equals(terminal)) {
State[] node = getGroupForState(
transitions[j].getToState(), tree);
if (!list.contains(node)) {
list.add(node);
}
}
}
}
return list;
}
/**
* Returns a TreeNode object in tree
whose user object is
* group
.
*
* @param tree
* the tree model to search.
* @param root
* the root of tree
* @param group
* the object to look for in tree.
* @return a TreeNode object in tree
whose user object is
* group
.
*/
// public DefaultMutableTreeNode getTreeNodeForObject
// (DefaultTreeModel tree, DefaultMutableTreeNode root, State[] group) {
public MinimizeTreeNode getTreeNodeForObject(DefaultTreeModel tree,
MinimizeTreeNode root, State[] group) {
State[] rootNode = (State[]) root.getUserObject();
if (rootNode == group) {
return root;
}
for (int k = 0; k < root.getChildCount(); k++) {
MinimizeTreeNode node = getTreeNodeForObject(tree,
(MinimizeTreeNode) root.getChildAt(k), group);
// DefaultMutableTreeNode node =
// getTreeNodeForObject(tree,
// (DefaultMutableTreeNode) root.getChildAt(k), group);
if (node != null)
return node;
}
return null;
}
/**
* Returns list of groups (as State[]) created by split of group
* on terminal
. Returns null if group is indistinguishable
* on terminal
.
*
* @param group
* the group to split.
* @param terminal
* the terminal to split group on.
* @param automaton
* the automaton.
* @return list of groups (as State[]) that represent distinguishable groups
* as subsets of group
.
*/
public ArrayList splitOnTerminal(State[] group, String terminal,
Automaton automaton, DefaultTreeModel tree) {
ArrayList newGroups = new ArrayList();
MinimizeTreeNode root = (MinimizeTreeNode) tree.getRoot();
ArrayList distinguishableGroups = getLeaves(tree, root);
Iterator it = distinguishableGroups.iterator();
// Iterator it = DISTINGUISHABLE_GROUPS.iterator();
while (it.hasNext()) {
ArrayList statesInGroup = new ArrayList();
State[] temp = (State[]) it.next();
for (int i = 0; i < group.length; i++) {
if (stateGoesToGroupOnTerminal(group[i], temp, terminal,
automaton)) {
statesInGroup.add(group[i]);
}
}
if (statesInGroup.size() > 0) {
State[] groupstates = (State[]) statesInGroup
.toArray(new State[0]);
newGroups.add(groupstates);
}
}
return newGroups;
}
/**
* Returns true if automaton
is minimized (i.e. there are no
* distinguishable groups).
*
* @param automaton
* the automaton.
* @return true if automaton
is minimized
*/
public boolean isMinimized(Automaton automaton, DefaultTreeModel tree) {
State[] states = getDistinguishableGroup(automaton, tree);
return states == null;
}
/**
* Returns list of groups (as State[]) obtained by splitting group
.
*
* @param group
* the group to split.
* @param automaton
* the automaton.
* @return list of groups (as State[]) obtained by splitting group
.
*/
public ArrayList split(State[] group, Automaton automaton,
DefaultTreeModel tree) {
String terminal = getTerminalToSplit(group, automaton, tree);
ArrayList list = new ArrayList();
list.addAll(splitOnTerminal(group, terminal, automaton, tree));
return list;
}
/**
* Returns true if there is a transition in transitions
with
* a label equal to terminal
. This assumes that the
* transitions are actually FSATransition objects.
*
* @param transitions
* the set of transitions.
* @param terminal
* the terminal.
* @return true if there is a transition on terminal.
*/
public boolean isTransitionOnTerminal(Transition[] transitions,
String terminal) {
for (int k = 0; k < transitions.length; k++) {
FSATransition transition = (FSATransition) transitions[k];
if (transition.getLabel().equals(terminal)) {
return true;
}
}
return false;
}
/**
* Returns true if automaton
needs a trap state. (i.e. there
* is an implied trap state since there are not transitions on every letter
* in the alphabet for every state in automaton
.)
*
* @param automaton
* the automaton.
* @return true if automaton needs a trap state.
*/
public boolean needsTrapState(Automaton automaton) {
AlphabetRetriever far = new FSAAlphabetRetriever();
String[] alphabet = far.getAlphabet(automaton);
State[] states = automaton.getStates();
for (int k = 0; k < states.length; k++) {
Transition[] transitions = automaton
.getTransitionsFromState(states[k]);
for (int j = 0; j < alphabet.length; j++) {
if (!isTransitionOnTerminal(transitions, alphabet[j])) {
return true;
}
}
}
return false;
}
/**
* Adds a trap state to automaton
and all implied transitions
* to that trap state. This actually alters the Automaton object itself.
*
* @param automaton
* the automaton.
*/
public void addTrapState(Automaton automaton) {
if (!needsTrapState(automaton))
return;
StatePlacer sp = new StatePlacer();
Point point = sp.getPointForState(automaton);
State trapState = automaton.createState(point);
TRAP_STATE = trapState;
AlphabetRetriever far = new FSAAlphabetRetriever();
String[] alphabet = far.getAlphabet(automaton);
State[] states = automaton.getStates();
for (int k = 0; k < states.length; k++) {
Transition[] transitions = automaton
.getTransitionsFromState(states[k]);
for (int j = 0; j < alphabet.length; j++) {
if (!isTransitionOnTerminal(transitions, alphabet[j])) {
FSATransition trans = new FSATransition(states[k],
trapState, alphabet[j]);
automaton.addTransition(trans);
}
}
}
}
/**
* Returns a minimizeable automaton obtained from automaton
* by removing all unreachable states and multiple character labels on
* transitions. This does not alter automaton
.
*
* @param a
* the automaton
* @return a minimizeable automaton obtained from automaton
* by removing all unreachable states and multiple character labels
* on transitions.
*/
public Automaton getMinimizeableAutomaton(Automaton a) {
// Automaton a = (Automaton) automaton.clone();
AutomatonChecker ac = new AutomatonChecker();
if (ac.isNFA(a))
return null;
/** Remove all unreachable states. */
UnreachableStatesDetector usd = new UnreachableStatesDetector(a);
State[] unreachableStates = usd.getUnreachableStates();
for (int k = 0; k < unreachableStates.length; k++) {
a.removeState(unreachableStates[k]);
}
/** Remove all multiple character labels. */
FSALabelHandler.removeMultipleCharacterLabelsFromAutomaton(a);
/** Remove the unnecessary states. */
// StateCleaner.clean(a);
/** Add trap state if necessary. */
addTrapState(a);
return a;
}
/**
* Prints the string representation of treeNode
to stdout
* (assumes that treeNode stores a StateNode object).
*
* @param treeNode
* the node to print.
*/
// public void printNode(DefaultMutableTreeNode treeNode) {
public void printNode(MinimizeTreeNode treeNode) {
State[] node = (State[]) treeNode.getUserObject();
System.out.print(getString(node));
// //System.out.println(" TERMINAL: " + treeNode.getTerminal());
}
/**
* Prints the string representation of tree
rooted at root
* to stdout. (assumes all nodes in tree contain StateNode objects).
*
* @param tree
* the tree to print.
* @param root
* the root of tree.
*/
// public void printTree(DefaultTreeModel tree, DefaultMutableTreeNode root)
// {
public void printTree(DefaultTreeModel tree, MinimizeTreeNode root) {
printNode(root);
for (int k = 0; k < root.getChildCount(); k++) {
MinimizeTreeNode child = (MinimizeTreeNode) root.getChildAt(k);
// DefaultMutableTreeNode child =
// (DefaultMutableTreeNode) root.getChildAt(k);
printTree(tree, child);
}
}
/**
* Returns the tree for automaton
with the root representing
* all states in automaton
, and then the root split into two
* children, one containing all final states in automaton
,
* the other containing all nonfinal states in automaton
.
*
* @param automaton
* the automaton being minimized.
* @return the tree of distinguishable groups with all states in automaton
* split into groups of final and nonfinal states.
*/
public DefaultTreeModel getInitializedTree(Automaton automaton) {
State[] states = automaton.getStates();
// DefaultMutableTreeNode root = new DefaultMutableTreeNode(states);
MinimizeTreeNode root = new MinimizeTreeNode(states);
DefaultTreeModel tree = new DefaultTreeModel(root);
ArrayList list = new ArrayList();
for (int k = 0; k < states.length; k++) {
if (!automaton.isFinalState(states[k]))
list.add(states[k]);
}
State[] nonFinalStates = (State[]) list.toArray(new State[0]);
int childIndex = 0;
if (nonFinalStates.length > 0) {
MinimizeTreeNode nfstates = new MinimizeTreeNode(nonFinalStates);
// DISTINGUISHABLE_GROUPS.add(nonFinalStates);
tree.insertNodeInto(nfstates, root, childIndex);
childIndex++;
}
State[] finalStates = automaton.getFinalStates();
// DefaultMutableTreeNode fstates =
// new DefaultMutableTreeNode(finalStates);
if (finalStates.length > 0) {
MinimizeTreeNode fstates = new MinimizeTreeNode(finalStates);
// DISTINGUISHABLE_GROUPS.add(finalStates);
tree.insertNodeInto(fstates, root, childIndex);
}
return tree;
}
/**
* Creates tree nodes for each group of states (State[]) in children
* and places them in tree
under parent
.
*
* @param children
* the list of groups that were obtained by splitting the group
* represented by parent
* @param parent
* the parent node in the tree
* @param tree
* the tree
*/
public void addChildrenToParent(ArrayList children,
MinimizeTreeNode parent, DefaultTreeModel tree) {
int index = 0;
Iterator it = children.iterator();
while (it.hasNext()) {
State[] childGroup = (State[]) it.next();
MinimizeTreeNode child = new MinimizeTreeNode(childGroup);
// DefaultMutableTreeNode child =
// new DefaultMutableTreeNode(childGroup);
tree.insertNodeInto(child, parent, index);
index++;
}
}
/**
* Returns the tree model of the process of distinguishing groups of states
* in automaton
.
*
* @param automaton
* the automaton.
* @return the tree model of the process of distinguishing groups of states
* in automaton
.
*/
public DefaultTreeModel getDistinguishableGroupsTree(Automaton automaton) {
DefaultTreeModel tree = getInitializedTree(automaton);
// DefaultMutableTreeNode root =
// (DefaultMutableTreeNode) tree.getRoot();
MinimizeTreeNode root = (MinimizeTreeNode) tree.getRoot();
while (!isMinimized(automaton, tree)) {
State[] group = getDistinguishableGroup(automaton, tree);
ArrayList children = new ArrayList();
String terminal = getTerminalToSplit(group, automaton, tree);
children.addAll(splitOnTerminal(group, terminal, automaton, tree));
// children.addAll(split(group, automaton));
MinimizeTreeNode parent = getTreeNodeForObject(tree, root, group);
parent.setTerminal(terminal);
// DefaultMutableTreeNode parent =
// getTreeNodeForObject(tree, root, group);
addChildrenToParent(children, parent, tree);
}
return tree;
}
/**
* Returns true if states
contains a state that is a final
* state in automaton
.
*
* @param states
* the set of states.
* @param automaton
* the automaton.
* @return true if states
contains a state that is a final
* state in automaton
.
*/
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 true if states
contains the initial state of
* automaton
.
*
* @param states
* the set of states.
* @param automaton
* the automaton.
* @return true if states
contains the initial state of
* automaton
.
*/
public boolean hasInitialState(State[] states, Automaton automaton) {
State initialState = automaton.getInitialState();
for (int k = 0; k < states.length; k++) {
if (states[k] == initialState)
return true;
}
return false;
}
/**
* Maps state
to states
*
* @param state
* the state
* @param group
* the group of states.
*/
public void mapStateToGroup(State state, State[] group) {
MAP.put(state, group);
}
/**
* Returns the group (State[]) mapped to state
.
*
* @param state
* the state
* @return the group (State[]) mapped to state
.
*/
public State[] getGroupMappedToState(State state) {
return (State[]) MAP.get(state);
}
/**
* Creates states in minDfa
for each group at a leaf in
* tree
.
*
* @param dfa
* the dfa being minimized
* @param minDfa
* the minimized form of dfa
.
* @param tree
* the distinguishable groups tree for the automaton being
* minimized.
*/
public void createStatesForMinimumDfa(Automaton dfa, Automaton minDfa,
DefaultTreeModel tree) {
MinimizeTreeNode root = (MinimizeTreeNode) tree.getRoot();
// DefaultMutableTreeNode root =
// (DefaultMutableTreeNode) tree.getRoot();
ArrayList groups = getLeaves(tree, root);
StatePlacer sp = new StatePlacer();
Iterator it = groups.iterator();
while (it.hasNext()) {
State[] group = (State[]) it.next();
if (!containsTrapState(group)) {
Point point = sp.getPointForState(minDfa);
State state = minDfa.createState(point);
state.setLabel(getString(group));
if (hasInitialState(group, dfa))
minDfa.setInitialState(state);
if (hasFinalState(group, dfa))
minDfa.addFinalState(state);
mapStateToGroup(state, group);
}
}
}
/**
* Returns the state from minDfa
that is mapped to group
*
* @param group
* the group of states
* @param minDfa
* the minimized dfa
* @return the state from minDfa
that is mapped to group
*/
public State getStateMappedToGroup(State[] group, Automaton minDfa) {
State[] states = minDfa.getStates();
for (int k = 0; k < states.length; k++) {
State[] tempGroup = getGroupMappedToState(states[k]);
if (tempGroup == group)
return states[k];
}
return null;
}
/**
* Returns a list of all transitions from state
in minDfa
,
* based on dfa
.
*
* @param state
* the state in the minimized dfa.
* @param minDfa
* the minimized dfa.
* @param dfa
* the dfa being minimized.
*/
public ArrayList getTransitionsForState(State state, Automaton minDfa,
Automaton dfa, DefaultTreeModel tree) {
ArrayList list = new ArrayList();
State[] group = getGroupMappedToState(state);
State stateInGroup = group[0];
Transition[] transitions = dfa.getTransitionsFromState(stateInGroup);
for (int k = 0; k < transitions.length; k++) {
FSATransition trans = (FSATransition) transitions[k];
State toState = trans.getToState();
State[] toGroup = getGroupForState(toState, tree);
if (!containsTrapState(toGroup)) {
State to = getStateMappedToGroup(toGroup, minDfa);
Transition transition = new FSATransition(state, to, trans
.getLabel());
list.add(transition);
}
}
return list;
}
/**
* Returns a list of State[] objects that are contained in the leaves of
* tree
*
* @param tree
* the tree
* @param root
* the root of tree
* @return a list of State[] objects that are contained in the leaves of
* tree
*/
// public ArrayList getLeaves(DefaultTreeModel tree,
// DefaultMutableTreeNode root) {
public ArrayList getLeaves(DefaultTreeModel tree, MinimizeTreeNode root) {
ArrayList list = new ArrayList();
if (tree.isLeaf(root)) {
State[] group = (State[]) root.getUserObject();
list.add(group);
}
for (int k = 0; k < root.getChildCount(); k++) {
MinimizeTreeNode child = (MinimizeTreeNode) root.getChildAt(k);
// DefaultMutableTreeNode child =
// (DefaultMutableTreeNode) root.getChildAt(k);
list.addAll(getLeaves(tree, child));
}
return list;
}
/**
* Returns true if states
contains the trap state
*
* @param states
* the states.
* @return true if states
contains the trap state
*/
public boolean containsTrapState(State[] states) {
for (int k = 0; k < states.length; k++) {
if (states[k] == TRAP_STATE)
return true;
}
return false;
}
/**
* Returns the minimized version of automaton
, for which
* tree
is the tree of distinguishable groups.
*
* @param automaton
* the automaton being minimized
* @param tree
* the tree of distinguishable groups for automaton
* @return the minimized version of automaton
, for which
* tree
is the tree of distinguishable groups.
*/
public FiniteStateAutomaton getMinimumDfa(Automaton automaton,
DefaultTreeModel tree) {
// if(isMinimized(automaton))
// return (FiniteStateAutomaton) automaton.clone();
FiniteStateAutomaton minDfa = new FiniteStateAutomaton();
createStatesForMinimumDfa(automaton, minDfa, tree);
ArrayList list = new ArrayList();
State[] states = minDfa.getStates();
for (int k = 0; k < states.length; k++) {
list.addAll(getTransitionsForState(states[k], minDfa, automaton,
tree));
}
Iterator it = list.iterator();
while (it.hasNext()) {
Transition transition = (Transition) it.next();
minDfa.addTransition(transition);
}
return minDfa;
}
/**
* the list of distinguishable groups at a given moment in the process of
* determining distinguishable groups.
*/
// protected ArrayList DISTINGUISHABLE_GROUPS;
/**
* map of states in minimum dfa to groups of states from non-minimized dfa.
*/
protected HashMap MAP;
/** the trap state added to dfa in order to minimize. */
protected State TRAP_STATE;
}