/**
  * Returns an automaton that accepts between <code>min</code> and <code>max</code> (including
  * both) concatenated repetitions of the language of the given automaton.
  *
  * <p>Complexity: linear in number of states and in <code>min</code> and <code>max</code>.
  */
 public static Automaton repeat(Automaton a, int min, int max) {
   if (min > max) return BasicAutomata.makeEmpty();
   max -= min;
   a.expandSingleton();
   Automaton b;
   if (min == 0) b = BasicAutomata.makeEmptyString();
   else if (min == 1) b = a.clone();
   else {
     List<Automaton> as = new ArrayList<Automaton>();
     while (min-- > 0) as.add(a);
     b = concatenate(as);
   }
   if (max > 0) {
     Automaton d = a.clone();
     while (--max > 0) {
       Automaton c = a.clone();
       for (State p : c.getAcceptStates()) p.addEpsilon(d.initial);
       d = c;
     }
     for (State p : b.getAcceptStates()) p.addEpsilon(d.initial);
     b.deterministic = false;
     // b.clearHashCode();
     b.clearNumberedStates();
     b.checkMinimizeAlways();
   }
   return b;
 }
 /**
  * Returns an automaton that accepts the Kleene star (zero or more concatenated repetitions) of
  * the language of the given automaton. Never modifies the input automaton language.
  *
  * <p>Complexity: linear in number of states.
  */
 public static Automaton repeat(Automaton a) {
   a = a.cloneExpanded();
   State s = new State();
   s.accept = true;
   s.addEpsilon(a.initial);
   for (State p : a.getAcceptStates()) p.addEpsilon(s);
   a.initial = s;
   a.deterministic = false;
   // a.clearHashCode();
   a.clearNumberedStates();
   a.checkMinimizeAlways();
   return a;
 }
 /**
  * Returns an automaton that accepts the concatenation of the languages of the given automata.
  *
  * <p>Complexity: linear in number of states.
  */
 public static Automaton concatenate(Automaton a1, Automaton a2) {
   if (a1.isSingleton() && a2.isSingleton())
     return BasicAutomata.makeString(a1.singleton + a2.singleton);
   if (isEmpty(a1) || isEmpty(a2)) return BasicAutomata.makeEmpty();
   // adding epsilon transitions with the NFA concatenation algorithm
   // in this case always produces a resulting DFA, preventing expensive
   // redundant determinize() calls for this common case.
   boolean deterministic = a1.isSingleton() && a2.isDeterministic();
   if (a1 == a2) {
     a1 = a1.cloneExpanded();
     a2 = a2.cloneExpanded();
   } else {
     a1 = a1.cloneExpandedIfRequired();
     a2 = a2.cloneExpandedIfRequired();
   }
   for (State s : a1.getAcceptStates()) {
     s.accept = false;
     s.addEpsilon(a2.initial);
   }
   a1.deterministic = deterministic;
   // a1.clearHashCode();
   a1.clearNumberedStates();
   a1.checkMinimizeAlways();
   return a1;
 }
 /**
  * Returns an automaton that accepts the union of the empty string and the language of the given
  * automaton.
  *
  * <p>Complexity: linear in number of states.
  */
 public static Automaton optional(Automaton a) {
   a = a.cloneExpandedIfRequired();
   State s = new State();
   s.addEpsilon(a.initial);
   s.accept = true;
   a.initial = s;
   a.deterministic = false;
   // a.clearHashCode();
   a.clearNumberedStates();
   a.checkMinimizeAlways();
   return a;
 }
 /**
  * Returns an automaton that accepts the concatenation of the languages of the given automata.
  *
  * <p>Complexity: linear in total number of states.
  */
 public static Automaton concatenate(List<Automaton> l) {
   if (l.isEmpty()) return BasicAutomata.makeEmptyString();
   boolean all_singleton = true;
   for (Automaton a : l)
     if (!a.isSingleton()) {
       all_singleton = false;
       break;
     }
   if (all_singleton) {
     StringBuilder b = new StringBuilder();
     for (Automaton a : l) b.append(a.singleton);
     return BasicAutomata.makeString(b.toString());
   } else {
     for (Automaton a : l) if (BasicOperations.isEmpty(a)) return BasicAutomata.makeEmpty();
     Set<Integer> ids = new HashSet<Integer>();
     for (Automaton a : l) ids.add(System.identityHashCode(a));
     boolean has_aliases = ids.size() != l.size();
     Automaton b = l.get(0);
     if (has_aliases) b = b.cloneExpanded();
     else b = b.cloneExpandedIfRequired();
     Set<State> ac = b.getAcceptStates();
     boolean first = true;
     for (Automaton a : l)
       if (first) first = false;
       else {
         if (a.isEmptyString()) continue;
         Automaton aa = a;
         if (has_aliases) aa = aa.cloneExpanded();
         else aa = aa.cloneExpandedIfRequired();
         Set<State> ns = aa.getAcceptStates();
         for (State s : ac) {
           s.accept = false;
           s.addEpsilon(aa.initial);
           if (s.accept) ns.add(s);
         }
         ac = ns;
       }
     b.deterministic = false;
     // b.clearHashCode();
     b.clearNumberedStates();
     b.checkMinimizeAlways();
     return b;
   }
 }
 /**
  * Returns an automaton that accepts the union of the languages of the given automata.
  *
  * <p>Complexity: linear in number of states.
  */
 public static Automaton union(Collection<Automaton> l) {
   Set<Integer> ids = new HashSet<Integer>();
   for (Automaton a : l) ids.add(System.identityHashCode(a));
   boolean has_aliases = ids.size() != l.size();
   State s = new State();
   for (Automaton b : l) {
     if (BasicOperations.isEmpty(b)) continue;
     Automaton bb = b;
     if (has_aliases) bb = bb.cloneExpanded();
     else bb = bb.cloneExpandedIfRequired();
     s.addEpsilon(bb.initial);
   }
   Automaton a = new Automaton();
   a.initial = s;
   a.deterministic = false;
   // a.clearHashCode();
   a.clearNumberedStates();
   a.checkMinimizeAlways();
   return a;
 }
 /**
  * Returns an automaton that accepts the union of the languages of the given automata.
  *
  * <p>Complexity: linear in number of states.
  */
 public static Automaton union(Automaton a1, Automaton a2) {
   if ((a1.isSingleton() && a2.isSingleton() && a1.singleton.equals(a2.singleton)) || a1 == a2)
     return a1.cloneIfRequired();
   if (a1 == a2) {
     a1 = a1.cloneExpanded();
     a2 = a2.cloneExpanded();
   } else {
     a1 = a1.cloneExpandedIfRequired();
     a2 = a2.cloneExpandedIfRequired();
   }
   State s = new State();
   s.addEpsilon(a1.initial);
   s.addEpsilon(a2.initial);
   a1.initial = s;
   a1.deterministic = false;
   // a1.clearHashCode();
   a1.clearNumberedStates();
   a1.checkMinimizeAlways();
   return a1;
 }