Skip to content

Commit

Permalink
Make Automata#optional create simpler automata. (#13793)
Browse files Browse the repository at this point in the history
In the common case when the input automaton has no transition to state 0, the
optional automaton can be created by marking state 0 as accepted.
  • Loading branch information
jpountz committed Sep 16, 2024
1 parent e7a6382 commit f4ebed2
Show file tree
Hide file tree
Showing 2 changed files with 97 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,37 @@ public static Automaton concatenate(List<Automaton> l) {
* <p>Complexity: linear in number of states.
*/
public static Automaton optional(Automaton a) {
if (a.isAccept(0)) {
// If the initial state is accepted, then the empty string is already accepted.
return a;
}

boolean hasTransitionsToInitialState = false;
Transition t = new Transition();
outer:
for (int state = 0; state < a.getNumStates(); ++state) {
int count = a.initTransition(state, t);
for (int i = 0; i < count; ++i) {
a.getNextTransition(t);
if (t.dest == 0) {
hasTransitionsToInitialState = true;
break outer;
}
}
}

if (hasTransitionsToInitialState == false) {
// If the automaton has no transition to the initial state, we can simply mark the initial
// state as accepted.
Automaton result = new Automaton();
result.copy(a);
if (result.getNumStates() == 0) {
result.createState();
}
result.setAccept(0, true);
return result;
}

Automaton result = new Automaton();
result.createState();
result.setAccept(0, true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -412,4 +412,70 @@ private static Automaton naiveRepeat(Automaton a) {

return builder.finish();
}

public void testOptional() {
Automaton a = Automata.makeChar('a');

Automaton optionalA = new Automaton();
optionalA.createState();
optionalA.setAccept(0, true);
optionalA.finishState();
optionalA.createState();
optionalA.setAccept(1, true);
optionalA.addTransition(0, 1, 'a');
optionalA.finishState();

assertTrue(AutomatonTestUtil.sameLanguage(Operations.optional(a), optionalA));
assertSame(optionalA, Operations.optional(optionalA));

// Now test an automaton that has a transition to state 0. a(ba)*
a = new Automaton();
a.createState();
a.createState();
a.setAccept(1, true);
a.addTransition(0, 1, 'a');
a.finishState();
a.addTransition(1, 0, 'b');
a.finishState();

optionalA = new Automaton();
optionalA.createState();
optionalA.setAccept(0, true);
optionalA.createState();
optionalA.createState();
optionalA.setAccept(2, true);
optionalA.addTransition(0, 2, 'a');
optionalA.finishState();
optionalA.addTransition(1, 2, 'a');
optionalA.finishState();
optionalA.addTransition(2, 1, 'b');
optionalA.finishState();

assertTrue(AutomatonTestUtil.sameLanguage(Operations.optional(a), optionalA));
assertSame(optionalA, Operations.optional(optionalA));
}

public void testDuelOptional() {
final int iters = atLeast(1_000);
for (int iter = 0; iter < iters; ++iter) {
Automaton a = AutomatonTestUtil.randomAutomaton(random());
Automaton repeat1 = Operations.determinize(Operations.optional(a), Integer.MAX_VALUE);
Automaton repeat2 = Operations.determinize(naiveOptional(a), Integer.MAX_VALUE);
assertTrue(AutomatonTestUtil.sameLanguage(repeat1, repeat2));
}
}

// This is the original implementation of Operations#optional, before we improved it to generate
// simpler automata in some common cases.
private static Automaton naiveOptional(Automaton a) {
Automaton result = new Automaton();
result.createState();
result.setAccept(0, true);
if (a.getNumStates() > 0) {
result.copy(a);
result.addEpsilon(0, 1);
}
result.finishState();
return result;
}
}

0 comments on commit f4ebed2

Please sign in to comment.