/* * (C) Copyright 2001 Arnaud Bailly (arnaud.oqube@gmail.com), * Yves Roos (yroos@lifl.fr) and others. * * Licensed under the Apache License, Version 2.0 (the License); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an AS IS BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package rationals; import rationals.transformations.TransformationsToolBox; import java.util.*; /** * A class defining Automaton objects * * This class defines the notion of automaton. Following notations are used to * describe this class. * <p> * An automaton is a 5-uple <em>A = (X , Q , I , T , D)</em> where * <ul> * <li><em>X</em> is a finite set of labels named alphabet , * <li><em>Q</em> is a finite set of states, * <li><em>I</em>, included in <em>Q</em>, is the set of initial states, * <li><em>T</em>, included in <em>Q</em>, is the set of terminal states * <li>and <em>D</em> is the set of transitions, which is included in * <em>Q times X times Q</em> (transitions are triples <em>(q , l , q')</em> where <em>q, q'</em> * are states and <em>l</em> a label). * </ul> * The empty word, usually denoted by <em>epsilon</em> will be denoted here by * the symbol <em>@</em>. * <p> * In this implementation of automaton, any object may be a label, states are * instance of class <tt>State</tt> and transitions are intances of class * <tt>Transition</tt>. Only automata should create instances of states through * <tt>Automaton</tt> method <tt>newState</tt>. * * @author yroos@lifl.fr * @author bailly@lifl.fr * @version $Id: Automaton.java 10 2007-05-30 17:25:00Z oqube $ * @see Transition State */ public class Automaton<L, Tr extends Transition<L>, T extends Builder<L, Tr, T>> implements Acceptor<L>, StateMachine<L>, Rational<L>, Cloneable { /* the identification of this automaton */ private Object id; protected Builder<L, Tr, T> builder; /** * @return Returns the id. */ public Object getId() { return id; } /** * @param id * The id to set. */ public void setId(Object id) { this.id = id; } // The set of all objects which are labels of // transitions of this automaton. protected Set<L> alphabet; // The set of all states of this automaton. private Set<State> states; // the set of initial states private Set<State> initials; // the set of terminal states private Set<State> terminals; // Allows access to transitions of this automaton // starting from a given state and labelled by // a given object. The keys of this map are instances // of class Key and // values are sets of transitions. private Map<Key, Set<Transition<L>>> transitions; // Allows access to transitions of this automaton // arriving to a given state and labelled by // a given object. The keys of this map are instances // of class Key and // values are sets of transitions. private Map<Key, Set<Transition<L>>> reverse; // bonte private StateFactory stateFactory = new DefaultStateFactory(this); private StateLabels stateLabels = new StateLabels(); /** * @return */ public StateFactory getStateFactory() { return this.stateFactory; } /** * @param factory */ public void setStateFactory(StateFactory factory) { this.stateFactory = factory; factory.setAutomaton(this); } /** * Returns an automaton which recognizes the regular language associated * with the regular expression <em>@</em>, where <em>@</em> denotes the * empty word. * * @return an automaton which recognizes <em>@</em> */ public static <L, Tr extends Transition<L>, T extends Builder<L, Tr, T>> Automaton<L, Tr, T> epsilonAutomaton() { Automaton<L, Tr, T> v = new Automaton<>(); v.addState(true, true); return v; } /** * Returns an automaton which recognizes the regular language associated * with the regular expression <em>l</em>, where <em>l</em> is a given * label. * * @param label * any object that will be used as a label. * @return an automaton which recognizes <em>label</em> */ public static <L, Tr extends Transition<L>, T extends Builder<L, Tr, T>> Automaton<L, Tr, T> labelAutomaton(L label) { Automaton<L, Tr, T> v = new Automaton<>(); State start = v.addState(true, false); State end = v.addState(false, true); try { v.addTransition(new Transition<L>(start, label, end)); } catch (NoSuchStateException x) { } return v; } /** * Returns an automaton which recognizes the regular language associated * with the regular expression <em>u</em>, where <em>u</em> is a given word. * * @param word * a List of Object interpreted as a word * @return an automaton which recognizes <em>label</em> */ public static <L, Tr extends Transition<L>, T extends Builder<L, Tr, T>> Automaton<L, Tr, T> labelAutomaton(List<L> word) { Automaton<L, Tr, T> v = new Automaton<>(); State start = null; if (word.isEmpty()) { v.addState(true, true); return v; } else start = v.addState(true, false); State end = null; try { for (Iterator<L> i = word.iterator(); i.hasNext();) { L o = i.next(); end = v.addState(false, !i.hasNext()); v.addTransition(new Transition<L>(start, o, end)); start = end; } } catch (NoSuchStateException x) { } return v; } /** * Creates a new empty automaton which contains no state and no transition. * An empty automaton recognizes the empty language. */ public Automaton() { this(null); } /** * Create a new empty automaton with given state factory. * * @param sf * the StateFactory object to use for creating new states. May be * null. */ public Automaton(StateFactory sf) { this.stateFactory = sf == null ? new DefaultStateFactory(this) : sf; alphabet = new HashSet<>(); states = stateFactory.stateSet(); initials = stateFactory.stateSet(); terminals = stateFactory.stateSet(); transitions = new HashMap<>(); reverse = new HashMap<>(); } /** * Returns a new instance of state which will be initial and terminal or not * depending of parameters. * * @param initial * if true, the new state will be initial; otherwise this state * will be non initial. * @param terminal * if true, the new state will be terminal; otherwise this state * will be non terminal. * @return a new state, associated with this automaton. This new state * should be used only with this automaton in order to create a new * transition for this automaton. * @see Transition */ public State addState(boolean initial, boolean terminal) { State state = stateFactory.create(initial, terminal); if (initial) initials.add(state); if (terminal) terminals.add(state); states.add(state); return state; } /** * Returns the alphabet <em>X</em> associated with this automaton. * * @return the alphabet <em>X</em> associated with this automaton. */ @Override public Set<L> alphabet() { return alphabet; } /** * Returns the set of states <em>Q</em> associated with this automaton. * * @return the set of states <em>Q</em> associated with this automaton. * Objects which are contained in this set are instances of class * <tt>State</tt>. * @see State */ public Set<State> states() { return states; } /** * Returns the set of initial states <em>I</em> associated with this * automaton. * * @return the set of initial states <em>I</em> associated with this * automaton. Objects which are contained in this set are instances * of class <tt>State</tt>. * @see State */ public Set<State> initials() { return initials; } /** * Returns the set of terminal states <em>T</em> associated with this * automaton. * * @return set of terminal states <em>T</em> associated with this automaton. * Objects which are contained in this set are instances of class * <tt>State</tt>. * @see State */ public Set<State> terminals() { return terminals; } // Computes and return the set of all accessible states, starting // from a given set of states and using transitions // contained in a given Map protected Set<State> access(Set<State> start, Map<Key, Set<Transition<L>>> map) { Set<State> current = start; Set<State> old; do { old = current; current = stateFactory.stateSet(); Iterator<State> i = old.iterator(); while (i.hasNext()) { State e = i.next(); current.add(e); Iterator<L> j = alphabet.iterator(); while (j.hasNext()) { Iterator<Transition<L>> k = find(map, e, j.next()).iterator(); while (k.hasNext()) { current.add(k.next().end()); } } } } while (current.size() != old.size()); return current; } /** * Returns the set of all accessible states in this automaton. * * @return the set of all accessible states in this automaton. A state * <em>s</em> is accessible if there exists a path from an initial * state to <em>s</em>. Objects which are contained in this set are * instances of class <tt>State</tt>. * @see State */ public Set<State> accessibleStates() { return access(initials, transitions); } /** * Returns the set of states that can be accessed in this automaton starting * from given set of states * * @param states * a non null set of starting states * @return a - possibly empty - set of accessible states */ public Set<State> accessibleStates(Set<State> states) { return access(states, transitions); } /* * (non-Javadoc) * * @see rationals.Rational#accessibleStates(rationals.State) */ public Set<State> accessibleStates(State state) { Set<State> s = stateFactory.stateSet(); s.add(state); return access(s, transitions); } /** * Returns the set of co-accesible states for a given set of states, that is * the set of states from this automaton from which there exists a path to a * state in <code>states</code>. * * @param states * a non null set of ending states * @return a - possibly empty - set of coaccessible states */ public Set<State> coAccessibleStates(Set<State> states) { return access(states, reverse); } /** * Returns the set of all co-accessible states in this automaton. * * @return the set of all co-accessible states in this automaton. A state * <em>s</em> is co-accessible if there exists a path from this * state <em>s</em> to a terminal state. Objects which are contained * in this set are instances of class <tt>State</tt>. * @see State */ public Set<State> coAccessibleStates() { return access(terminals, reverse); } /** * Returns the set of all states which are co-accessible and accessible in * this automaton. * * @return the set of all states which are co-accessible and accessible in * this automaton. A state <em>s</em> is accessible if there exists * a path from an initial state to <em>s</em>. A state <em>s</em> is * co-accessible if there exists a path from this state <em>s</em> * to a terminal state. Objects which are contained in this set are * instances of class <tt>State</tt>. * @see State */ public Set<State> accessibleAndCoAccessibleStates() { Set<State> ac = accessibleStates(); ac.retainAll(coAccessibleStates()); return ac; } // Computes and return the set of all transitions, starting // from a given state and labelled by a given label // contained in a given Map protected Set<Transition<L>> find(Map<Key, Set<Transition<L>>> m, State e, L l) { Key n = new Key(e, l); if (!m.containsKey(n)) return new HashSet<Transition<L>>(); return m.get(n); } // add a given transition in a given Map protected void add(Map<Key, Set<Transition<L>>> m, Transition<L> t) { Key n = new Key(t.start(), t.label()); Set<Transition<L>> s; if (!m.containsKey(n)) { s = new HashSet<>(); m.put(n, s); } else s = m.get(n); s.add(t); } /** * Returns the set of all transitions of this automaton * * @return the set of all transitions of this automaton Objects which are * contained in this set are instances of class <tt>Transition</tt>. * @see Transition */ public Set<Transition<L>> delta() { Set<Transition<L>> s = new HashSet<>(); for (Set<Transition<L>> tr : transitions.values()) s.addAll(tr); return s; } /** * Returns the set of all transitions of this automaton starting from a * given state and labelled b a given label. * * @param state * a state of this automaton. * @param label * a label used in this automaton. * @return the set of all transitions of this automaton starting from state * <tt>state</tt> and labelled by <tt>label</tt>. Objects which are * contained in this set are instances of class <tt>Transition</tt>. * @see Transition */ public Set<Transition<L>> delta(State state, L label) { return find(transitions, state, label); } /** * Returns the set of all transitions from state <code>from</code> to state * <code>to</code>. * * @param from * starting state * @param to * ending state * @return a Set of Transition objects */ @Override public Set<Transition<L>> deltaFrom(State from, State to) { Set<Transition<L>> t = delta(from); for (Iterator<Transition<L>> i = t.iterator(); i.hasNext();) { Transition<L> tr = i.next(); if (!to.equals(tr.end())) i.remove(); } return t; } /** * Return all transitions from a State * * @param state * start state * @return a new Set of transitions (maybe empty) */ public Set<Transition<L>> delta(State state) { Set<Transition<L>> s = new HashSet<>(); for (L lt : alphabet) s.addAll(delta(state, lt)); return s; } /** * Returns all transitions from a given set of states. * * @param s * a Set of State objects * @return a Set of Transition objects */ public Set<Transition<L>> delta(Set<State> s) { Set<Transition<L>> ds = new HashSet<>(); for (State st : s) ds.addAll(delta(st)); return ds; } /** * Return a mapping from couples (q,q') of states to all (q,l,q') * transitions from q to q' * * @return a Map */ public Map<Couple, Set<Transition<L>>> couples() { // loop on transition map keys Iterator<Map.Entry<Key, Set<Transition<L>>>> it = transitions.entrySet().iterator(); Map<Couple, Set<Transition<L>>> ret = new HashMap<>(); while (it.hasNext()) { Map.Entry<Key, Set<Transition<L>>> e = it.next(); // get start and end state State st = e.getKey().s; Iterator<Transition<L>> trans = e.getValue().iterator(); while (trans.hasNext()) { Transition<L> tr = trans.next(); State nd = tr.end(); Couple cpl = new Couple(st, nd); Set<Transition<L>> s = ret.get(cpl); if (s == null) s = new HashSet<>(); s.add(tr); ret.put(cpl, s); } } return ret; } /** * Returns the set of all transitions of the reverse of this automaton * * @return the set of all transitions of the reverse of this automaton. A * reverse of an automaton <em>A = (X , Q , I , T , D)</em> is the * automaton <em>A' = (X , Q , T , I , D')</em> where <em>D'</em> is the set <em>{ (q , l , q') | (q' , l , q) in D}</em>. Objects * which are contained in this set are instances of class * <tt>Transition</tt>. * @see Transition */ @Override public Set<Transition<L>> deltaMinusOne(State state, L label) { return find(reverse, state, label); } /** * Adds a new transition in this automaton if it is a new transition for * this automaton. The parameter is considered as a new transition if there * is no transition in this automaton which is equal to the parameter in the * sense of method <tt>equals</tt> of class <tt>Transition</tt>. * * @param transition * the transition to add. * @throws NoSuchStateException * if <tt>transition</tt> is <tt>null</<tt> * or if <tt>transition</tt> = <em>(q , l , q')</em> and <em>q</em> or <em>q'</em> does * not belong to <em>Q</em> the set of the states of this * automaton. */ public void addTransition(Transition<L> transition) throws NoSuchStateException { if (!states.contains(transition.start()) || !states.contains(transition.end())) throw new NoSuchStateException(); if (!alphabet.contains(transition.label())) { alphabet.add(transition.label()); } add(transitions, transition); add(reverse, new Transition<>(transition.end(), transition.label(), transition.start())); } /** * the project method keeps from the Automaton only the transitions labelled * with the letters contained in the set alph, effectively computing a * projection on this alphabet. * * @param alph * the alphabet to project on */ public void projectOn(Set<?> alph) { // remove unwanted transitions from ret Iterator<Map.Entry<Key, Set<Transition<L>>>> trans = transitions.entrySet().iterator(); Set<Transition<L>> newtrans = new HashSet<>(); while (trans.hasNext()) { Map.Entry<Key, Set<Transition<L>>> entry = trans.next(); Key k = entry.getKey(); Iterator<Transition<L>> tit = entry.getValue().iterator(); while (tit.hasNext()) { Transition<?> tr = tit.next(); if (!alph.contains(k.l)) { // create epsilon transition newtrans.add(new Transition<L>(k.s, null, tr.end())); // remove transition tit.remove(); } } } // add newly created transitions if (!newtrans.isEmpty()) { for (Transition<L> tr : newtrans) { add(transitions, tr); add(reverse, new Transition<>(tr.end(), tr.label(), tr.start())); } } // remove alphabet alphabet.retainAll(alph); } /** * returns a textual representation of this automaton. * * @return a textual representation of this automaton based on the converter * <tt>toAscii</tt>. * @see rationals.converters.toAscii */ public String toString() { return new rationals.converters.toAscii().toString(this); } /** * returns a copy of this automaton. * * @return a copy of this automaton with new instances of states and * transitions. */ @Override public Automaton<L, Tr, T> clone() { Automaton<L, Tr, T> b = new Automaton<L, Tr, T>(); Map<State, State> map = new HashMap<>(); for (State e : states) map.put(e, b.addState(e.isInitial(), e.isTerminal())); for (Transition<L> t : delta()) { try { b.addTransition(new Transition<>(map.get(t.start()), t.label(), map.get(t.end()))); } catch (NoSuchStateException x) { } } return b; } /** * * @return the set of labels matching initial states. */ public Set<Object> labelledInitials() { return stateLabels.labels(initials()); } /** * * @return the set of labels for terminal states. */ public Set<Object> labelledTerminals() { return stateLabels.labels(terminals()); } /** * * @return labels for states of this automaton. */ public Set<Object> labelledStates() { return stateLabels.labels(states); } private class Key { private State s; private L l; protected Key(State s, L l) { this.s = s; this.l = l; } public boolean equals(Object o) { if (o == null || !(o instanceof Automaton.Key)) return false; Key t = (Key) o; boolean ret = (l == null ? t.l == null : l.equals(t.l)) && (s == null ? t.s == null : s.equals(t.s)); return ret; } public int hashCode() { int x, y; if (s == null) x = 0; else x = s.hashCode(); if (l == null) y = 0; else y = l.hashCode(); return y << 16 | x; } } /** * Returns true if this automaton accepts given word -- ie. sequence of * letters. Note that this method accepts words with letters not in this * automaton's alphabet, effectively recognizing all words from any alphabet * projected to this alphabet. * <p> * If you need standard recognition, use * * @see{accept(java.util.List) . * @param word * @return */ public boolean prefixProjection(List<L> word) { Set<?> s = stepsProject(word); return !s.isEmpty(); } /** * Return the set of steps this automaton will be in after reading word. * Note this method skips letters not in alphabet instead of rejecting them. * * @param l * @return */ public Set<State> stepsProject(List<L> word) { Set<State> s = initials(); Iterator<L> it = word.iterator(); while (it.hasNext()) { L o = it.next(); if (!alphabet.contains(o)) continue; s = step(s, o); if (s.isEmpty()) return s; } return s; } /* * (non-Javadoc) * * @see rationals.Acceptor#accept(java.util.List) */ @Override public boolean accept(List<L> word) { Set<State> s = TransformationsToolBox.epsilonClosure(steps(word), this); s.retainAll(terminals()); return !s.isEmpty(); } /** * Return true if this automaton can accept the given word starting from * given set. <em>Note</em> The ending state(s) need not be terminal for * this method to return true. * * @param state * a starting state * @param word * a List of objects in this automaton's alphabet * @return true if there exists a path labelled by word from s to at least * one other state in this automaton. */ public boolean accept(State state, List<L> word) { Set<State> s = stateFactory.stateSet(); s.add(state); return !steps(s, word).isEmpty(); } /* * (non-Javadoc) * * @see rationals.Acceptor#steps(java.util.List) */ @Override public Set<State> steps(List<L> word) { Set<State> s = TransformationsToolBox.epsilonClosure(initials(), this); return steps(s, word); } /** * Return the set of states this automaton will be in after reading the word * from start states s. * * @param s * the set of starting states * @param word * the word to read. * @return the set of reached states. */ @Override public Set<State> steps(Set<State> s, List<L> word) { Iterator<L> it = word.iterator(); while (it.hasNext()) { L o = it.next(); s = step(s, o); if (s.isEmpty()) return s; } return s; } /** * Return the set of states this automaton will be in after reading the word * from single start state s. * * @param st * the starting state * @param word * the word to read. * @return the set of reached states. */ @Override public Set<State> steps(State st, List<L> word) { Set<State> s = stateFactory.stateSet(); s.add(st); Iterator<L> it = word.iterator(); while (it.hasNext()) { L o = it.next(); s = step(s, o); if (s.isEmpty()) return s; } return s; } /** * Return the list of set of states this automaton will be in after reading * word from start state. Is start state is null, assume reading from * initials(). * * @param word * @param start */ @Override public List<Set<State>> traceStates(List<L> word, State start) { List<Set<State>> ret = new ArrayList<Set<State>>(); Set<State> s = null; if (start != null) { s = stateFactory.stateSet(); s.add(start); } else { s = initials(); } Iterator<L> it = word.iterator(); while (it.hasNext()) { L o = it.next(); if (!alphabet.contains(o)) continue; s = step(s, o); ret.add(s); if (s.isEmpty()) return null; } return ret; } /** * Returns the size of the longest word recognized by this automaton where * letters not belonging to its alphabet are ignored. * * * @param word * @return */ public int longestPrefixWithProjection(List<L> word) { int lret = 0; Set<State> s = initials(); Iterator<L> it = word.iterator(); while (it.hasNext()) { L o = it.next(); if ((o == null) || !alphabet.contains(o)) { lret++; continue; } s = step(s, o); if (s.isEmpty()) break; lret++; } return lret; } /** * Return the set of states accessible in one transition from given set of * states s and letter o. * * @param s * @param o * @return */ public Set<State> step(Set<State> s, L o) { Set<State> ns = stateFactory.stateSet(); Set<State> ec = TransformationsToolBox.epsilonClosure(s, this); Iterator<State> it = ec.iterator(); while (it.hasNext()) { State st = it.next(); Iterator<?> it2 = delta(st).iterator(); while (it2.hasNext()) { Transition<?> tr = (Transition<?>) it2.next(); if (tr.label() != null && tr.label().equals(o)) ns.add(tr.end()); } } return ns; } /** * @param tr * @param msg */ public void updateTransitionWith(Transition<L> tr, L msg) { L lbl = tr.label(); alphabet.remove(lbl); alphabet.add(msg); /* update transition map */ Key k = new Key(tr.start(), lbl); Set<Transition<L>> s = transitions.remove(k); if (s != null) transitions.put(new Key(tr.start(), msg), s); /* update reverse map */ k = new Key(tr.end(), lbl); s = reverse.remove(k); if (s != null) reverse.put(new Key(tr.end(), msg), s); tr.setLabel(msg); } /** * @param st * @return */ @Override public Set<Transition<L>> deltaMinusOne(State st) { Set<Transition<L>> s = new HashSet<>(); Iterator<L> alphit = alphabet().iterator(); while (alphit.hasNext()) { s.addAll(deltaMinusOne(st, alphit.next())); } return s; } /** * Enumerate all prefix of words of length lower or equal than i in this * automaton. This method takes exponential time and space to execute: <em> * use with care !</em> * . * * @param i * maximal length of words. * @return a Set of List of Object */ public Set<List<L>> enumerate(int ln) { Set<List<L>> ret = new HashSet<>(); class EnumState { /** * @param s * @param list */ public EnumState(State s, List<L> list) { st = s; word = new ArrayList<L>(list); } State st; List<L> word; } ; LinkedList<EnumState> ll = new LinkedList<EnumState>(); List<L> cur = new ArrayList<>(); for (Iterator<State> i = initials.iterator(); i.hasNext();) { State s = i.next(); if (s.isTerminal()) ret.add(new ArrayList<L>()); ll.add(new EnumState(s, cur)); } do { EnumState st = ll.removeFirst(); Set<Transition<L>> trs = delta(st.st); List<L> word = st.word; for (Iterator<Transition<L>> k = trs.iterator(); k.hasNext();) { Transition<L> tr = k.next(); word.add(tr.label()); if (word.size() <= ln) { EnumState en = new EnumState(tr.end(), word); ll.add(en); ret.add(en.word); } word.remove(word.size() - 1); } } while (!ll.isEmpty()); return ret; } /** * Create a new state with given label. The state is created with as neither * initial nor terminal. * * @param label * the state's label. May not be null. * @return the newly created state. */ public State state(L label) { State s = stateLabels.state(label); if (s == null) { s = stateFactory.create(false, false, label); states.add(s); stateLabels.bind(s, label); } return s; } /** * Starts creation of a new transition from the given state. Note that the * state is created with given label if it does not exists. * * @param o * the label of state to create transition from. may not be null. * @return a TransitionBuilder that can be used to create a new transition. */ public T from(L o) { return builder.build(state(o), this); } public void setBuilder(Builder<L, Tr, T> t) { this.builder = t; this.builder.setAutomaton(this); } public void build(State from, L l, State to) throws NoSuchStateException { addTransition(this.builder.build(from, l, to)); } }