
org.leibnizcenter.nfa.NFA Maven / Gradle / Ivy
Show all versions of nfa Show documentation
package org.leibnizcenter.nfa;
import com.github.krukow.clj_ds.PersistentList;
import com.google.common.collect.*;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* Immutable NFA
*
* Created by maarten on 15-6-16.
*/
@SuppressWarnings("WeakerAccess")
public class NFA {
public final Map>> transitions;
public final Set states;
public final Multimap statesThatAllowEvent;
private NFA(Builder builder) {
this.states = ImmutableSet.copyOf(builder.states);
ImmutableMap.Builder>> immTransitions = new ImmutableMap.Builder<>();
// O(transitions.numberOfBranches())
builder.transitions.forEach((state, eventToTransitionMap) -> {
final ImmutableMultimap.Builder> eventToTransitionMapBuilder = new ImmutableMultimap.Builder<>();
eventToTransitionMap.forEach(eventToTransitionMapBuilder::putAll);
immTransitions.put(state, eventToTransitionMapBuilder.build());
});
this.transitions = immTransitions.build();
// O(transitions.numberOfBranches())
ImmutableMultimap.Builder immStatesThatAllowEvent = new ImmutableMultimap.Builder<>();
builder.transitions.forEach((state, eventMap) ->
eventMap.forEach((event, transitions) ->
immStatesThatAllowEvent.put(event, state)
)
);
statesThatAllowEvent = immStatesThatAllowEvent.build();
//
// Sanity check:
//
// O(transitions.numberOfBranches())
builder.transitions.forEach((state, eventMap) -> {
assert (eventMap.values().stream()
.flatMap(Collection::stream)
.map(Transition::getFrom)
.filter(st4t3 -> !state.equals(st4t3))
.limit(1).count() == 0) : "Error in map from state " + state + " to transitions. This is a bug.";
}
);
}
@SuppressWarnings("unused")
public PossibleStateTransitionPaths getTransitions(S start, LinkedList events) {
final List events1 = ImmutableList.copyOf(events); // O(n)
return precomputePaths(events)
.get(start)
.get(events1);
}
public Stream apply(S start, LinkedList events) {
final PossibleStateTransitionPaths transitions = getTransitions(start, events);
return transitions.applyRecursive();
}
/**
* O(path.numberOfBranches() * states.numberOfBranches() * transitions.numberOfBranches())
*
* @param event Input events to use for computing all possible paths along the NFA
* @return A map from starting states to a map of input events to an enumeration of possible branches
*/
public Map, PossibleStateTransitionPaths>> precomputePaths(LinkedList event) {
PersistentList postFixPath = com.github.krukow.clj_lang.PersistentList.create((Iterable extends E>) new ArrayList(0)); // O(1)
Map, PossibleStateTransitionPaths>> precomputedPaths = new HashMap<>(states.size());// O(1)
while (event.size() > 0) { // O(path.numberOfBranches()) *
E lastEvent = event.removeLast(); // O(1)
postFixPath = postFixPath.plus(lastEvent); // O(1)
// TODO filter only those states that *can* be reached through the previous action
for (S state : statesThatAllowEvent.get(lastEvent)) { // O(states.numberOfBranches()) *
Map, PossibleStateTransitionPaths> pathsForEvents = precomputedPaths.getOrDefault(state, new HashMap<>());//O(1)
final Collection> possibleTransitions = getTransitions(state, lastEvent); // O(1)
PossibleStateTransitionPaths possibleBranches;
if (postFixPath.size() == 1) {
possibleBranches = new PossibleStateTransitionPaths<>(state, possibleTransitions, postFixPath, null); // O(1)
} else {
final PersistentList furtherEvents = postFixPath.minus();
ImmutableMap.Builder> restPaths = ImmutableMap.builder(); // O(1)
possibleTransitions.stream() // O(possibleTransitions.numberOfBranches())
.map(Transition::getTo)
.distinct()
.forEach(possibleTargetState -> {
PossibleStateTransitionPaths restBranches = precomputedPaths.get(possibleTargetState).get(furtherEvents);
assert restBranches != null : "Possible branches must have been calculated for state " + possibleTargetState;
restPaths.put(possibleTargetState, restBranches);
});
possibleBranches = new PossibleStateTransitionPaths<>(
state,
possibleTransitions,
postFixPath,
restPaths.build()
); // O(possibleTransitions.numberOfBranches())
}
assert !pathsForEvents.containsKey(postFixPath) : "Already computed possible paths for " + postFixPath + "?!";
pathsForEvents.put(postFixPath, possibleBranches); // O(1)
precomputedPaths.putIfAbsent(state, pathsForEvents);// O(1)
}
}
return precomputedPaths;
}
public Collection> getTransitions(S from, E event) {
final Multimap> eventTransitionMultimap = transitions.get(from);
if (eventTransitionMultimap != null) return eventTransitionMultimap.get(event);
else return Collections.emptySet();
}
public Set getStates() {
return states;
}
public StateContainer start(S state) {
return new StateContainer(Collections.singletonList(state));
}
@SuppressWarnings("unused")
public Collection getStatesThatAllowEvent(E e) {
return statesThatAllowEvent.get(e);
}
public static class Builder {
private final Set states;
private final Map>>> transitions;
public Builder() {
this.states = new HashSet<>(50);
transitions = new HashMap<>(50);
}
@SuppressWarnings("unused")
public Builder addStates(Collection states) {
this.states.addAll(states);
return this;
}
public Builder addState(S states) {
this.states.add(states);
return this;
}
/**
* Will automatically add states if they've not been added separately.
*
* @param from From state
* @param event Transition event
* @param to To state
* @return This builder
*/
public Builder addTransition(S from, E event, S to) {
states.add(from);
states.add(to);
addTransition(new Transition<>(event, from, to), from, event);
return this;
}
/**
* Will automatically add states if they've not been added separately.
*
* @param transitionsToAdd List of transitions. Implicit states will be added if necessary.
* @return This builder
*/
public Builder addTransitions(Collection transitionsToAdd) {
transitionsToAdd.forEach(this::addTransition);
return this;
}
/**
* Will automatically add states if they've not been added separately.
*
* @param transition Implicit states will be added if necessary.
* @return This builder
*/
public Builder addTransition(Transition transition) {
S from = transition.from;
S to = transition.to;
E event = transition.event;
states.add(from);
states.add(to);
addTransition(transition, from, event);
return this;
}
private void addTransition(Transition transition, S from, E event) {
Map>> eventsForState = transitions.getOrDefault(from, new HashMap<>());
Set> transitionsForEvent = eventsForState.getOrDefault(event, new HashSet<>());
transitionsForEvent.add(transition);
eventsForState.putIfAbsent(event, transitionsForEvent);
transitions.putIfAbsent(from, eventsForState);
}
public NFA build() {
return new NFA<>(this);
}
}
public class StateContainer {
public Collection states;
public StateContainer(Collection ses) {
states = ses;
}
public StateContainer andThen(E e) {
states = states.stream()
.flatMap(from -> getTransitions(from, e).stream().map(transition -> {//noinspection unchecked
e.accept(transition.getFrom(), transition.getTo());
return transition;
})).map(Transition::getTo).collect(Collectors.toList());
return this;
}
public Stream getState() {
return states.stream();
}
}
}