Skip to content

Commit 4fc065b

Browse files
author
Maarten
committed
Add parking meter example
1 parent 62b31f2 commit 4fc065b

File tree

11 files changed

+528
-159
lines changed

11 files changed

+528
-159
lines changed

README.md

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,86 @@ On a side note, [Allauzen & Mohri](http://www.cs.nyu.edu/~allauzen/pdf/twins.pdf
1414
* 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.
1515
* 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*|).
1616
* 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.
17+
18+
## Example
19+
Here is a simple example of a parking meter that takes money:
20+
21+
```java
22+
public class ParkingMeter {
23+
/**
24+
* How much money is in the machine
25+
*/
26+
private int ¢¢¢;
27+
28+
public static void main(String[] args) {
29+
// Run example
30+
new ParkingMeter().run();
31+
}
32+
33+
public void run() {
34+
// Say we can buy parking for 100 cents
35+
36+
// Define some actions
37+
CoinDrop drop25cents = new CoinDrop(25);
38+
CoinDrop drop50cents = new CoinDrop(50);
39+
40+
// Define our NFA
41+
NFA<PayState, CoinDrop> nfa = new NFA.Builder<PayState, CoinDrop>()
42+
.addTransition(PAYED_0, drop25cents, PAYED_25)
43+
.addTransition(PAYED_0, drop50cents, PAYED_50)
44+
.addTransition(PAYED_25, drop25cents, PAYED_50)
45+
.addTransition(PAYED_25, drop50cents, PAYED_75)
46+
.addTransition(PAYED_50, drop25cents, PAYED_75)
47+
.addTransition(PAYED_50, drop50cents, PAYED_0)
48+
.addTransition(PAYED_75, drop25cents, PAYED_0)
49+
.addTransition(PAYED_75, drop50cents, PAYED_0) // Payed too much... no money back!
50+
.build();
51+
52+
// Apply action step-by-step
53+
Collection<State> endStates1 = nfa.start(PAYED_0)
54+
.andThen(drop25cents)
55+
.andThen(drop50cents)
56+
.andThen(drop50cents)
57+
.andThen(drop25cents)
58+
.getState().collect(Collectors.toList());
59+
60+
// Or apply actions in bulk (this makes calculations of the possible paths more efficient, but it doesn't matter if we iterate over all transitions anyway)
61+
Collection<State> endStates2 = nfa.apply(PAYED_0, new LinkedList<>(Arrays.asList(drop50cents, drop25cents, drop50cents, drop25cents)))
62+
.collect(Collectors.toList());
63+
64+
System.out.println("Today earnings: ¢" + ¢¢¢ + ".");
65+
}
66+
67+
private class CoinDrop implements Event<PayState> {
68+
final int ¢;
69+
70+
CoinDrop(int value) {
71+
this.¢ = value;
72+
}
73+
74+
@Override
75+
public void accept(PayState from, PayState to) {
76+
System.out.println("Bleep Bloop. Added ¢" + ¢ + " to ¢" + from.¢ + ". ");
77+
if (to.¢ <= 0 || to.¢ >= 100) System.out.println("You may park. Good day.");
78+
else
79+
System.out.println("You have paid ¢" + to.¢ + " in total. Please add ¢" + (100 - to.¢) + " before you may park.");
80+
System.out.println("----------------------------------------------");
81+
¢¢¢ += this.¢;
82+
}
83+
84+
@Override
85+
public String toString() {
86+
return "¢" + ¢;
87+
}
88+
}
89+
90+
enum PayState implements State {
91+
PAYED_0(0), PAYED_25(25), PAYED_50(50), PAYED_75(75);
92+
public int ¢;
93+
94+
PayState(int ¢) {
95+
this.¢ = ¢;
96+
}
97+
}
98+
}
99+
```
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package org.leibnizcenter.nfa;
22

3+
import java.util.function.BiConsumer;
4+
35
/**
46
* Created by maarten on 16-6-16.
57
*/
6-
public interface Event extends Runnable {
8+
public interface Event<S extends State> extends BiConsumer<S, S> {
79
}

src/main/java/org/leibnizcenter/nfa/NFA.java

Lines changed: 81 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -4,32 +4,35 @@
44
import com.google.common.collect.*;
55

66
import java.util.*;
7+
import java.util.stream.Collectors;
8+
import java.util.stream.Stream;
79

810
/**
911
* Immutable NFA
1012
* <p>
1113
* Created by maarten on 15-6-16.
1214
*/
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;
15+
@SuppressWarnings("WeakerAccess")
16+
public class NFA<S extends State, E extends Event> {
17+
public final Map<S, Multimap<E, Transition<S, E>>> transitions;
18+
public final Set<S> states;
19+
public final Multimap<E, S> statesThatAllowEvent;
1720

18-
private NFA(Builder builder) {
21+
private NFA(Builder<S, E> builder) {
1922
this.states = ImmutableSet.copyOf(builder.states);
2023

21-
ImmutableMap.Builder<State, Multimap<Event, Transition>> immTransitions = new ImmutableMap.Builder<>();
24+
ImmutableMap.Builder<S, Multimap<E, Transition<S, E>>> immTransitions = new ImmutableMap.Builder<>();
2225

2326
// O(transitions.numberOfBranches())
2427
builder.transitions.forEach((state, eventToTransitionMap) -> {
25-
final ImmutableMultimap.Builder<Event, Transition> eventToTransitionMapBuilder = new ImmutableMultimap.Builder<>();
28+
final ImmutableMultimap.Builder<E, Transition<S, E>> eventToTransitionMapBuilder = new ImmutableMultimap.Builder<>();
2629
eventToTransitionMap.forEach(eventToTransitionMapBuilder::putAll);
2730
immTransitions.put(state, eventToTransitionMapBuilder.build());
2831
});
2932
this.transitions = immTransitions.build();
3033

3134
// O(transitions.numberOfBranches())
32-
ImmutableMultimap.Builder<Event, State> immStatesThatAllowEvent = new ImmutableMultimap.Builder<>();
35+
ImmutableMultimap.Builder<E, S> immStatesThatAllowEvent = new ImmutableMultimap.Builder<>();
3336
builder.transitions.forEach((state, eventMap) ->
3437
eventMap.forEach((event, transitions) ->
3538
immStatesThatAllowEvent.put(event, state)
@@ -52,48 +55,54 @@ private NFA(Builder builder) {
5255
);
5356
}
5457

55-
public PossibleStateTransitionPaths getPathsForInput(State start, LinkedList<Event> events) {
56-
final List<Event> events1 = ImmutableList.copyOf(events); // O(n)
58+
@SuppressWarnings("unused")
59+
public PossibleStateTransitionPaths<S, E> getTransitions(S start, LinkedList<E> events) {
60+
final List<E> events1 = ImmutableList.copyOf(events); // O(n)
5761
return precomputePaths(events)
5862
.get(start)
5963
.get(events1);
6064
}
6165

66+
public Stream<State> apply(S start, LinkedList<E> events) {
67+
final PossibleStateTransitionPaths<S, E> transitions = getTransitions(start, events);
68+
return transitions.applyRecursive();
69+
}
70+
6271
/**
6372
* O(path.numberOfBranches() * states.numberOfBranches() * transitions.numberOfBranches())
6473
*
6574
* @param event Input events to use for computing all possible paths along the NFA
6675
* @return A map from starting states to a map of input events to an enumeration of possible branches
6776
*/
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)
77+
public Map<S, Map<List<E>, PossibleStateTransitionPaths<S, E>>> precomputePaths(LinkedList<E> event) {
78+
PersistentList<E> postFixPath = com.github.krukow.clj_lang.PersistentList.create((Iterable<? extends E>) new ArrayList<E>(0)); // O(1)
79+
Map<S, Map<List<E>, PossibleStateTransitionPaths<S, E>>> precomputedPaths = new HashMap<>(states.size());// O(1)
7180

7281
while (event.size() > 0) { // O(path.numberOfBranches()) *
73-
Event lastEvent = event.removeLast(); // O(1)
82+
E lastEvent = event.removeLast(); // O(1)
7483

7584
postFixPath = postFixPath.plus(lastEvent); // O(1)
7685

7786
// 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)
87+
for (S state : statesThatAllowEvent.get(lastEvent)) { // O(states.numberOfBranches()) *
88+
Map<List<E>, PossibleStateTransitionPaths<S, E>> pathsForEvents = precomputedPaths.getOrDefault(state, new HashMap<>());//O(1)
89+
final Collection<Transition<S, E>> possibleTransitions = getTransitions(state, lastEvent); // O(1)
8190

82-
PossibleStateTransitionPaths possibleBranches;
91+
PossibleStateTransitionPaths<S, E> possibleBranches;
8392
if (postFixPath.size() == 1) {
84-
possibleBranches = new PossibleStateTransitionPaths(state, possibleTransitions, postFixPath, null); // O(1)
93+
possibleBranches = new PossibleStateTransitionPaths<>(state, possibleTransitions, postFixPath, null); // O(1)
8594
} else {
86-
final PersistentList<Event> furtherEvents = postFixPath.minus();
87-
ImmutableMap.Builder<State, PossibleStateTransitionPaths> restPaths = ImmutableMap.builder(); // O(1)
95+
final PersistentList<E> furtherEvents = postFixPath.minus();
96+
ImmutableMap.Builder<S, PossibleStateTransitionPaths<S, E>> restPaths = ImmutableMap.builder(); // O(1)
8897
possibleTransitions.stream() // O(possibleTransitions.numberOfBranches())
8998
.map(Transition::getTo)
9099
.distinct()
91100
.forEach(possibleTargetState -> {
92-
PossibleStateTransitionPaths restBranches = precomputedPaths.get(possibleTargetState).get(furtherEvents);
101+
PossibleStateTransitionPaths<S, E> restBranches = precomputedPaths.get(possibleTargetState).get(furtherEvents);
93102
assert restBranches != null : "Possible branches must have been calculated for state " + possibleTargetState;
94103
restPaths.put(possibleTargetState, restBranches);
95104
});
96-
possibleBranches = new PossibleStateTransitionPaths(
105+
possibleBranches = new PossibleStateTransitionPaths<>(
97106
state,
98107
possibleTransitions,
99108
postFixPath,
@@ -108,32 +117,41 @@ public Map<State, Map<List<Event>, PossibleStateTransitionPaths>> precomputePath
108117
return precomputedPaths;
109118
}
110119

111-
public Collection<Transition> getTransitions(State from, Event event) {
112-
final Multimap<Event, Transition> eventTransitionMultimap = transitions.get(from);
113-
120+
public Collection<Transition<S, E>> getTransitions(S from, E event) {
121+
final Multimap<E, Transition<S, E>> eventTransitionMultimap = transitions.get(from);
114122
if (eventTransitionMultimap != null) return eventTransitionMultimap.get(event);
115123
else return Collections.emptySet();
116124
}
117125

118-
public Set<State> getStates() {
126+
public Set<S> getStates() {
119127
return states;
120128
}
121129

122-
public static class Builder {
123-
private final Set<State> states;
124-
private final Map<State, Map<Event, Set<Transition>>> transitions;
130+
public StateContainer start(S state) {
131+
return new StateContainer(Collections.singletonList(state));
132+
}
133+
134+
@SuppressWarnings("unused")
135+
public Collection<S> getStatesThatAllowEvent(E e) {
136+
return statesThatAllowEvent.get(e);
137+
}
138+
139+
public static class Builder<S extends State, E extends Event> {
140+
private final Set<S> states;
141+
private final Map<S, Map<E, Set<Transition<S, E>>>> transitions;
125142

126143
public Builder() {
127144
this.states = new HashSet<>(50);
128145
transitions = new HashMap<>(50);
129146
}
130147

131-
public Builder addStates(Collection<State> states) {
148+
@SuppressWarnings("unused")
149+
public Builder<S, E> addStates(Collection<S> states) {
132150
this.states.addAll(states);
133151
return this;
134152
}
135153

136-
public Builder addState(State states) {
154+
public Builder<S, E> addState(S states) {
137155
this.states.add(states);
138156
return this;
139157
}
@@ -146,11 +164,11 @@ public Builder addState(State states) {
146164
* @param to To state
147165
* @return This builder
148166
*/
149-
public Builder addTransition(State from, Event event, State to) {
167+
public Builder<S, E> addTransition(S from, E event, S to) {
150168
states.add(from);
151169
states.add(to);
152170

153-
addTransition(new Transition(event, from, to), from, event);
171+
addTransition(new Transition<>(event, from, to), from, event);
154172

155173
return this;
156174
}
@@ -161,7 +179,7 @@ public Builder addTransition(State from, Event event, State to) {
161179
* @param transitionsToAdd List of transitions. Implicit states will be added if necessary.
162180
* @return This builder
163181
*/
164-
public Builder addTransitions(Collection<Transition> transitionsToAdd) {
182+
public Builder<S, E> addTransitions(Collection<Transition> transitionsToAdd) {
165183
transitionsToAdd.forEach(this::addTransition);
166184
return this;
167185
}
@@ -172,31 +190,48 @@ public Builder addTransitions(Collection<Transition> transitionsToAdd) {
172190
* @param transition Implicit states will be added if necessary.
173191
* @return This builder
174192
*/
175-
public Builder addTransition(Transition transition) {
176-
State from = transition.from;
177-
State to = transition.to;
178-
Event event = transition.event;
193+
public Builder<S, E> addTransition(Transition<S, E> transition) {
194+
S from = transition.from;
195+
S to = transition.to;
196+
E event = transition.event;
179197
states.add(from);
180198
states.add(to);
181199
addTransition(transition, from, event);
182200
return this;
183201
}
184202

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<>());
203+
private void addTransition(Transition<S, E> transition, S from, E event) {
204+
Map<E, Set<Transition<S, E>>> eventsForState = transitions.getOrDefault(from, new HashMap<>());
205+
Set<Transition<S, E>> transitionsForEvent = eventsForState.getOrDefault(event, new HashSet<>());
188206
transitionsForEvent.add(transition);
189207
eventsForState.putIfAbsent(event, transitionsForEvent);
190208
transitions.putIfAbsent(from, eventsForState);
191209
}
192210

193211

194-
public NFA build() {
195-
return new NFA(this);
212+
public NFA<S, E> build() {
213+
return new NFA<>(this);
196214
}
197215
}
198216

199-
public Collection<State> getStatesThatAllowEvent(Event e) {
200-
return statesThatAllowEvent.get(e);
217+
public class StateContainer {
218+
public Collection<S> states;
219+
220+
public StateContainer(Collection<S> ses) {
221+
states = ses;
222+
}
223+
224+
public StateContainer andThen(E e) {
225+
states = states.stream()
226+
.flatMap(from -> getTransitions(from, e).stream().map(transition -> {//noinspection unchecked
227+
e.accept(transition.getFrom(), transition.getTo());
228+
return transition;
229+
})).map(Transition::getTo).collect(Collectors.toList());
230+
return this;
231+
}
232+
233+
public Stream<S> getState() {
234+
return states.stream();
235+
}
201236
}
202237
}

0 commit comments

Comments
 (0)