Skip to content

Commit f428ccb

Browse files
author
Maarten
committed
First commit
0 parents  commit f428ccb

File tree

15 files changed

+977
-0
lines changed

15 files changed

+977
-0
lines changed

.gitignore

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Created by .ignore support plugin (hsz.mobi)
2+
### Java template
3+
*.class
4+
5+
# Mobile Tools for Java (J2ME)
6+
.mtj.tmp/
7+
8+
# Package Files #
9+
*.jar
10+
*.war
11+
*.ear
12+
13+
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
14+
hs_err_pid*
15+
16+
.idea/
17+
*.iml
18+
gradle/
19+
gradlew
20+
*.gradle
21+
gradlew.*

LICENSE.txt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2016 Maarten Trompper
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Nondeterministic finite state automata
2+
This is a library that implement [nondeterminstic finite state atuomata](https://en.wikipedia.org/wiki/Nondeterministic_finite_automaton) (NFAs) in Java. You can think of NFAs as flowcharts: you are in a state, take some action, and arrive in a new state. The action can produce a side effect, such as writing a string to a tape.
3+
4+
## Why?
5+
There are already a bunch of libraries out there which work with deterministic finite state automata, and there is a well-known result in automata theory which says that for any language recognized by an NFA, we can construct a DFA which recognizes the same language.
6+
7+
There are two problems, however:
8+
* Determinizing an NFA has an exponential blowup in the number of states (|stated(DFA)| = O(|states(NFA)|²)
9+
* An NFA may have side-effects, which may be problematic with the standard way of determinizing NFAs. Indeed, [some non-deterministic finite state transducers have no equivalent deterministic finite state transducer](http://www.let.rug.nl/~vannoord/papers/preds/node22.html).
10+
11+
On a side note, [Allauzen & Mohri](http://www.cs.nyu.edu/~allauzen/pdf/twins.pdf) have described efficient algorithms for determining when a transducer is in fact determinizable, and this would be a nice feature to implement.
12+
13+
## Current features
14+
* Arbitrary input tokens, and arbitrary side effect to state transitions. For example we may implement a finite state transducer by taking strings as input tokens and writing some output string to tape as a side effect.
15+
* Compute possible transition paths in polynomial time! Using a [forward-backward-like algorithm](https://en.wikipedia.org/wiki/Forward%E2%80%93backward_algorithm), we can compute all paths through automaton *A* originating from state *S*, given input *I* all possible paths in O(|*S*| * |*I*| * |*A*|).
16+
* Transition paths can be accessed through a Spliterator: Java 8 streaming APIs can automatically branch transition paths on states where one action may lead to multiple result states.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package org.leibnizcenter.nfa;
2+
3+
/**
4+
* Created by maarten on 16-6-16.
5+
*/
6+
public interface Event extends Runnable {
7+
}
Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
package org.leibnizcenter.nfa;
2+
3+
import com.github.krukow.clj_ds.PersistentList;
4+
import com.google.common.collect.*;
5+
6+
import java.util.*;
7+
8+
/**
9+
* Immutable NFA
10+
* <p>
11+
* Created by maarten on 15-6-16.
12+
*/
13+
public class NFA {
14+
public final Map<State, Multimap<Event, Transition>> transitions;
15+
public final Set<State> states;
16+
public final Multimap<Event, State> statesThatAllowEvent;
17+
18+
private NFA(Builder builder) {
19+
this.states = ImmutableSet.copyOf(builder.states);
20+
21+
ImmutableMap.Builder<State, Multimap<Event, Transition>> immTransitions = new ImmutableMap.Builder<>();
22+
23+
// O(transitions.numberOfBranches())
24+
builder.transitions.forEach((state, eventToTransitionMap) -> {
25+
final ImmutableMultimap.Builder<Event, Transition> eventToTransitionMapBuilder = new ImmutableMultimap.Builder<>();
26+
eventToTransitionMap.forEach(eventToTransitionMapBuilder::putAll);
27+
immTransitions.put(state, eventToTransitionMapBuilder.build());
28+
});
29+
this.transitions = immTransitions.build();
30+
31+
// O(transitions.numberOfBranches())
32+
ImmutableMultimap.Builder<Event, State> immStatesThatAllowEvent = new ImmutableMultimap.Builder<>();
33+
builder.transitions.forEach((state, eventMap) ->
34+
eventMap.forEach((event, transitions) ->
35+
immStatesThatAllowEvent.put(event, state)
36+
)
37+
);
38+
statesThatAllowEvent = immStatesThatAllowEvent.build();
39+
40+
//
41+
// Sanity check:
42+
//
43+
44+
// O(transitions.numberOfBranches())
45+
builder.transitions.forEach((state, eventMap) -> {
46+
assert (eventMap.values().stream()
47+
.flatMap(Collection::stream)
48+
.map(Transition::getFrom)
49+
.filter(st4t3 -> !state.equals(st4t3))
50+
.limit(1).count() == 0) : "Error in map from state " + state + " to transitions. This is a bug.";
51+
}
52+
);
53+
}
54+
55+
public PossibleStateTransitionPaths getPathsForInput(State start, LinkedList<Event> events) {
56+
final List<Event> events1 = ImmutableList.copyOf(events); // O(n)
57+
return precomputePaths(events)
58+
.get(start)
59+
.get(events1);
60+
}
61+
62+
/**
63+
* O(path.numberOfBranches() * states.numberOfBranches() * transitions.numberOfBranches())
64+
*
65+
* @param event Input events to use for computing all possible paths along the NFA
66+
* @return A map from starting states to a map of input events to an enumeration of possible branches
67+
*/
68+
public Map<State, Map<List<Event>, PossibleStateTransitionPaths>> precomputePaths(LinkedList<Event> event) {
69+
PersistentList<Event> postFixPath = com.github.krukow.clj_lang.PersistentList.create(); // O(1)
70+
Map<State, Map<List<Event>, PossibleStateTransitionPaths>> precomputedPaths = new HashMap<>(states.size());// O(1)
71+
72+
while (event.size() > 0) { // O(path.numberOfBranches()) *
73+
Event lastEvent = event.removeLast(); // O(1)
74+
75+
postFixPath = postFixPath.plus(lastEvent); // O(1)
76+
77+
// TODO filter only those states that *can* be reached through the previous action
78+
for (State state : statesThatAllowEvent.get(lastEvent)) { // O(states.numberOfBranches()) *
79+
Map<List<Event>, PossibleStateTransitionPaths> pathsForEvents = precomputedPaths.getOrDefault(state, new HashMap<>());//O(1)
80+
final Collection<Transition> possibleTransitions = getTransitions(state, lastEvent); // O(1)
81+
82+
PossibleStateTransitionPaths possibleBranches;
83+
if (postFixPath.size() == 1) {
84+
possibleBranches = new PossibleStateTransitionPaths(state, possibleTransitions, postFixPath, null); // O(1)
85+
} else {
86+
final PersistentList<Event> furtherEvents = postFixPath.minus();
87+
ImmutableMap.Builder<State, PossibleStateTransitionPaths> restPaths = ImmutableMap.builder(); // O(1)
88+
possibleTransitions.stream() // O(possibleTransitions.numberOfBranches())
89+
.map(Transition::getTo)
90+
.distinct()
91+
.forEach(possibleTargetState -> {
92+
PossibleStateTransitionPaths restBranches = precomputedPaths.get(possibleTargetState).get(furtherEvents);
93+
assert restBranches != null : "Possible branches must have been calculated for state " + possibleTargetState;
94+
restPaths.put(possibleTargetState, restBranches);
95+
});
96+
possibleBranches = new PossibleStateTransitionPaths(
97+
state,
98+
possibleTransitions,
99+
postFixPath,
100+
restPaths.build()
101+
); // O(possibleTransitions.numberOfBranches())
102+
}
103+
assert !pathsForEvents.containsKey(postFixPath) : "Already computed possible paths for " + postFixPath + "?!";
104+
pathsForEvents.put(postFixPath, possibleBranches); // O(1)
105+
precomputedPaths.putIfAbsent(state, pathsForEvents);// O(1)
106+
}
107+
}
108+
return precomputedPaths;
109+
}
110+
111+
public Collection<Transition> getTransitions(State from, Event event) {
112+
final Multimap<Event, Transition> eventTransitionMultimap = transitions.get(from);
113+
114+
if (eventTransitionMultimap != null) return eventTransitionMultimap.get(event);
115+
else return Collections.emptySet();
116+
}
117+
118+
public Set<State> getStates() {
119+
return states;
120+
}
121+
122+
public static class Builder {
123+
private final Set<State> states;
124+
private final Map<State, Map<Event, Set<Transition>>> transitions;
125+
126+
public Builder() {
127+
this.states = new HashSet<>(50);
128+
transitions = new HashMap<>(50);
129+
}
130+
131+
public Builder addStates(Collection<State> states) {
132+
this.states.addAll(states);
133+
return this;
134+
}
135+
136+
public Builder addState(State states) {
137+
this.states.add(states);
138+
return this;
139+
}
140+
141+
/**
142+
* Will automatically add states if they've not been added separately.
143+
*
144+
* @param from From state
145+
* @param event Transition event
146+
* @param to To state
147+
* @return This builder
148+
*/
149+
public Builder addTransition(State from, Event event, State to) {
150+
states.add(from);
151+
states.add(to);
152+
153+
addTransition(new Transition(event, from, to), from, event);
154+
155+
return this;
156+
}
157+
158+
/**
159+
* Will automatically add states if they've not been added separately.
160+
*
161+
* @param transitionsToAdd List of transitions. Implicit states will be added if necessary.
162+
* @return This builder
163+
*/
164+
public Builder addTransitions(Collection<Transition> transitionsToAdd) {
165+
transitionsToAdd.forEach(this::addTransition);
166+
return this;
167+
}
168+
169+
/**
170+
* Will automatically add states if they've not been added separately.
171+
*
172+
* @param transition Implicit states will be added if necessary.
173+
* @return This builder
174+
*/
175+
public Builder addTransition(Transition transition) {
176+
State from = transition.from;
177+
State to = transition.to;
178+
Event event = transition.event;
179+
states.add(from);
180+
states.add(to);
181+
addTransition(transition, from, event);
182+
return this;
183+
}
184+
185+
private void addTransition(Transition transition, State from, Event event) {
186+
Map<Event, Set<Transition>> eventsForState = transitions.getOrDefault(from, new HashMap<>());
187+
Set<Transition> transitionsForEvent = eventsForState.getOrDefault(event, new HashSet<>());
188+
transitionsForEvent.add(transition);
189+
eventsForState.putIfAbsent(event, transitionsForEvent);
190+
transitions.putIfAbsent(from, eventsForState);
191+
}
192+
193+
194+
public NFA build() {
195+
return new NFA(this);
196+
}
197+
}
198+
199+
public Collection<State> getStatesThatAllowEvent(Event e) {
200+
return statesThatAllowEvent.get(e);
201+
}
202+
}

0 commit comments

Comments
 (0)