├── .travis.yml ├── src ├── main │ └── java │ │ └── org │ │ └── leibnizcenter │ │ └── nfa │ │ ├── State.java │ │ ├── Event.java │ │ ├── util │ │ ├── Pair.java │ │ └── Collections3.java │ │ ├── Transition.java │ │ ├── demo │ │ └── ParkingMeter.java │ │ ├── NFA.java │ │ └── PossibleStateTransitionPaths.java └── test │ └── java │ └── org │ └── leibnizcenter │ └── nfa │ ├── TStates.java │ ├── TEvents.java │ ├── BuilderTest.java │ ├── NFATest.java │ └── ParkingMeterTest.java ├── .gitignore ├── deprecated └── TransitionStream.java ├── LICENSE.txt ├── pom.xml └── README.md /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | install: mvn install -Dgpg.skip=true 3 | 4 | jdk: 5 | - oraclejdk8 -------------------------------------------------------------------------------- /src/main/java/org/leibnizcenter/nfa/State.java: -------------------------------------------------------------------------------- 1 | package org.leibnizcenter.nfa; 2 | 3 | /** 4 | * Created by maarten on 15-6-16. 5 | */ 6 | public interface State { 7 | } 8 | -------------------------------------------------------------------------------- /src/test/java/org/leibnizcenter/nfa/TStates.java: -------------------------------------------------------------------------------- 1 | package org.leibnizcenter.nfa; 2 | 3 | /** 4 | * Created by maarten on 16-6-16. 5 | */ 6 | public enum TStates implements State { 7 | S0, S1, S3 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/org/leibnizcenter/nfa/Event.java: -------------------------------------------------------------------------------- 1 | package org.leibnizcenter.nfa; 2 | 3 | import java.util.function.BiConsumer; 4 | 5 | /** 6 | * Created by maarten on 16-6-16. 7 | */ 8 | public interface Event extends BiConsumer { 9 | } 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 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.* 22 | target/ -------------------------------------------------------------------------------- /src/test/java/org/leibnizcenter/nfa/TEvents.java: -------------------------------------------------------------------------------- 1 | package org.leibnizcenter.nfa; 2 | 3 | /** 4 | * Created by maarten on 16-6-16. 5 | */ 6 | public final class TEvents implements Event { 7 | public static final TEvents eventC = new TEvents("c"); 8 | public static final TEvents eventB = new TEvents("b"); 9 | public static final TEvents eventA = new TEvents("a"); 10 | 11 | private final String name; 12 | 13 | public TEvents(String name) { 14 | this.name = name; 15 | } 16 | 17 | @Override 18 | public String toString() { 19 | return "[" + name + "]"; 20 | } 21 | 22 | @Override 23 | public void accept(TStates tStates, TStates tStates2) { 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /deprecated/TransitionStream.java: -------------------------------------------------------------------------------- 1 | //package org.leibnizcenter.nfa; 2 | // 3 | //import java.util.Comparator; 4 | //import java.util.Iterator; 5 | //import java.util.Optional; 6 | //import java.util.Spliterator; 7 | //import java.util.function.*; 8 | //import java.util.stream.*; 9 | // 10 | ///** 11 | // * Wrapper for stream to house some utility methods for NFAs 12 | // *

13 | // * Created by maarten on 18-6-16. 14 | // */ 15 | //@SuppressWarnings("unused") 16 | //public class TransitionStream { 17 | // private final NFA nfa; 18 | // private Stream> stream; 19 | // 20 | // public TransitionStream(NFA nfa, Stream> stream) { 21 | // this.stream = stream; 22 | // this.nfa = nfa; 23 | // } 24 | // 25 | // public TransitionStream andThen(E event) { 26 | // stream = stream 27 | // 28 | // ; 29 | // return this; 30 | // } 31 | // 32 | // 33 | // public Stream getState() { 34 | // 35 | // } 36 | //} 37 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 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. -------------------------------------------------------------------------------- /src/main/java/org/leibnizcenter/nfa/util/Pair.java: -------------------------------------------------------------------------------- 1 | package org.leibnizcenter.nfa.util; 2 | 3 | import java.util.Map; 4 | 5 | /** 6 | * A pair of elements 7 | *

8 | * Created by Maarten on 2016-04-03. 9 | */ 10 | public class Pair implements Map.Entry { 11 | private V v; 12 | private K k; 13 | 14 | public Pair(K key, V value) { 15 | v = value; 16 | k = key; 17 | } 18 | 19 | @Override 20 | public K getKey() { 21 | return k; 22 | } 23 | 24 | @Override 25 | public V getValue() { 26 | return v; 27 | } 28 | 29 | @Override 30 | public V setValue(V value) { 31 | V oldV = v; 32 | v = value; 33 | return oldV; 34 | } 35 | 36 | @SuppressWarnings("unused") 37 | public K setKey(K key) { 38 | K oldKey = k; 39 | k = key; 40 | return oldKey; 41 | } 42 | 43 | @Override 44 | public boolean equals(Object o) { 45 | if (this == o) return true; 46 | if (!(o instanceof Map.Entry)) return false; 47 | 48 | Map.Entry pair = (Map.Entry) o; 49 | 50 | return (v != null ? v.equals(pair.getValue()) : pair.getValue() == null) 51 | && (k != null ? k.equals(pair.getKey()) : pair.getKey() == null); 52 | } 53 | 54 | @Override 55 | public int hashCode() { 56 | return 31 * (v != null ? v.hashCode() : 0) + (k != null ? k.hashCode() : 0); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/test/java/org/leibnizcenter/nfa/BuilderTest.java: -------------------------------------------------------------------------------- 1 | package org.leibnizcenter.nfa; 2 | 3 | import com.google.common.collect.Sets; 4 | import org.junit.Test; 5 | 6 | import java.util.Arrays; 7 | 8 | import static org.junit.Assert.assertEquals; 9 | import static org.leibnizcenter.nfa.TStates.S0; 10 | import static org.leibnizcenter.nfa.TStates.S1; 11 | 12 | /** 13 | * Created by maarten on 16-6-16. 14 | */ 15 | public class BuilderTest { 16 | 17 | @Test 18 | public void addState() { 19 | NFA.Builder b = new NFA.Builder<>(); 20 | b.addState(S1); 21 | assertEquals(Sets.newHashSet(S1), b.build().getStates()); 22 | } 23 | 24 | @Test 25 | public void addTransition() { 26 | NFA.Builder b = new NFA.Builder<>(); 27 | final TEvents event = TEvents.eventC; 28 | b.addTransition(S0, event, S0); 29 | b.addTransition(S1, event, S1); 30 | b.addTransition(S0, event, S1); 31 | final NFA nfa = b.build(); 32 | assertEquals(Sets.newHashSet(S1, S0), nfa.getStates()); 33 | assertEquals(2, nfa.getTransitions(S0, event).size()); 34 | } 35 | 36 | @Test 37 | public void addTransitions() { 38 | NFA.Builder> b = new NFA.Builder<>(); 39 | final TEvents event = TEvents.eventC; 40 | Transition> e1 = new Transition<>(event, S0, S1); 41 | Transition> e2 = new Transition<>(S0, event, S0); 42 | Transition> e3 = Transition.from(S1).through(event).to(S1); 43 | b.addTransitions(Arrays.asList( 44 | e1, 45 | e2, 46 | e3 47 | )); 48 | final NFA> nfa = b.build(); 49 | assertEquals(Sets.newHashSet(S1, S0), nfa.getStates()); 50 | assertEquals(2, nfa.getTransitions(S0, event).size()); 51 | } 52 | 53 | 54 | } -------------------------------------------------------------------------------- /src/main/java/org/leibnizcenter/nfa/Transition.java: -------------------------------------------------------------------------------- 1 | package org.leibnizcenter.nfa; 2 | 3 | @SuppressWarnings({"WeakerAccess", "unused"}) 4 | public class Transition> { 5 | public final EventType event; 6 | public final StateType from; 7 | public final StateType to; 8 | public final boolean isFinal; 9 | 10 | public Transition(EventType event, StateType from, StateType to) { 11 | this.event = event; 12 | this.from = from; 13 | this.to = to; 14 | this.isFinal = false; 15 | } 16 | 17 | public Transition(EventType event, StateType from, StateType to, boolean isFinal) { 18 | this.event = event; 19 | this.from = from; 20 | this.to = to; 21 | this.isFinal = isFinal; 22 | } 23 | 24 | public Transition(StateType from, EventType event, StateType to) { 25 | this.event = event; 26 | this.from = from; 27 | this.to = to; 28 | this.isFinal = false; 29 | } 30 | 31 | public static > 32 | FromHolder from(StateType from) { 33 | return new FromHolder<>(from); 34 | } 35 | 36 | public EventType getEvent() { 37 | return event; 38 | } 39 | 40 | public StateType getFrom() { 41 | return from; 42 | } 43 | 44 | public StateType getTo() { 45 | return to; 46 | } 47 | 48 | public boolean isFinal() { 49 | return isFinal; 50 | } 51 | 52 | @Override 53 | public String toString() { 54 | return from + "-[" + event + 55 | "]->" + to; 56 | } 57 | 58 | 59 | @SuppressWarnings("rawtypes") 60 | @Override 61 | public boolean equals(Object o) { 62 | if (this == o) return true; 63 | if (o == null || getClass() != o.getClass()) return false; 64 | 65 | Transition that = (Transition) o; 66 | 67 | return isFinal == that.isFinal && event.equals(that.event) && from.equals(that.from) && to.equals(that.to); 68 | 69 | } 70 | 71 | @Override 72 | public int hashCode() { 73 | int result = event.hashCode(); 74 | result = 31 * result + from.hashCode(); 75 | result = 31 * result + to.hashCode(); 76 | result = 31 * result + (isFinal ? 1 : 0); 77 | return result; 78 | } 79 | 80 | public static class FromHolder> { 81 | private final StateType from; 82 | 83 | public FromHolder(StateType from) { 84 | this.from = from; 85 | } 86 | 87 | public ThroughHolder through(EventType event) { 88 | return new ThroughHolder<>(from, event); 89 | } 90 | } 91 | 92 | public static class ThroughHolder> { 93 | private final EventType event; 94 | private final StateType from; 95 | 96 | public ThroughHolder(StateType from, EventType event) { 97 | this.event = event; 98 | this.from = from; 99 | } 100 | 101 | public Transition to(StateType to) { 102 | return new Transition<>(from, event, to); 103 | } 104 | } 105 | } -------------------------------------------------------------------------------- /src/main/java/org/leibnizcenter/nfa/util/Collections3.java: -------------------------------------------------------------------------------- 1 | package org.leibnizcenter.nfa.util; 2 | 3 | import java.util.*; 4 | import java.util.stream.Stream; 5 | 6 | /** 7 | * Extra util methods for collections 8 | *

9 | * Created by Maarten on 2016-04-03. 10 | */ 11 | @SuppressWarnings("unused") 12 | public class Collections3 { 13 | /** 14 | * Runs in O(1), but the stream may run longer of course. 15 | * 16 | * @return A stream of pairs of elements taken from stream A and stream B 17 | */ 18 | public static Stream> zip(Stream as, Stream bs) { 19 | final Iterator i = as.iterator(); 20 | return bs.filter(x -> i.hasNext()).map(b -> new Pair<>(i.next(), b)); 21 | } 22 | 23 | /** 24 | * Runs in O(1). 25 | * 26 | * @param coll Given collection 27 | * @return Whether the given collection is null or {@link Collection#isEmpty() empty} 28 | */ 29 | public static boolean isNullOrEmpty(Collection coll) { 30 | return coll == null || coll.isEmpty(); 31 | } 32 | 33 | /** 34 | * Runs in O(k), where k is the element at which the parameter stopAfter occurs (or the length of the given iterable if it does not occur) 35 | * 36 | * @param stopAfter Element after which to stop traversing the deque 37 | * @return Given iterable as a {@link LinkedList linked list}, up to but not including stopAfter 38 | */ 39 | public static LinkedList upToAndIncluding(Iterable iterable, K stopAfter) { 40 | final LinkedList returnObj = new LinkedList<>(); 41 | for (K el : iterable) { 42 | returnObj.add(el); 43 | if (Objects.equals(el, stopAfter)) break; 44 | } 45 | return returnObj; 46 | } 47 | 48 | /** 49 | * @return Given list, or immutable empty list if null 50 | */ 51 | @SuppressWarnings("unchecked") 52 | public static List orEmpty(List list) { 53 | return (list == null) ? Collections.EMPTY_LIST : list; 54 | } 55 | 56 | /** 57 | * @return Given list, or immutable empty list if null 58 | */ 59 | @SuppressWarnings("unchecked") 60 | public static Set orEmpty(Set set) { 61 | return set == null ? Collections.EMPTY_SET : set; 62 | } 63 | 64 | /** 65 | * @return 0 if collection is null 66 | */ 67 | public static int size(Collection collection) { 68 | return collection == null ? 0 : collection.size(); 69 | } 70 | 71 | /** 72 | * @return Last element in list, of null if not existing 73 | */ 74 | public static R last(List list) { 75 | return (list == null || list.size() <= 0) ? null : list.get(list.size() - 1); 76 | } 77 | 78 | /** 79 | * @return Sub-list starting at start (inclusive), ending at list end 80 | */ 81 | public static List subList(List l, int start) { 82 | List newList = new ArrayList<>(l.size() - start); 83 | for (int i = start; i < l.size(); i++) newList.add(l.get(i)); 84 | return newList; 85 | } 86 | 87 | /** 88 | * @param set May be null 89 | * @param toAdd Element to add 90 | * @return Given set with element added (or new HashSet if given set was null) 91 | */ 92 | public static Set add(Set set, T toAdd) { 93 | if (set == null) set = new HashSet<>(); 94 | set.add(toAdd); 95 | return set; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/main/java/org/leibnizcenter/nfa/demo/ParkingMeter.java: -------------------------------------------------------------------------------- 1 | package org.leibnizcenter.nfa.demo; 2 | 3 | import org.leibnizcenter.nfa.Event; 4 | import org.leibnizcenter.nfa.NFA; 5 | import org.leibnizcenter.nfa.State; 6 | 7 | import java.util.Arrays; 8 | import java.util.Collection; 9 | import java.util.LinkedList; 10 | import java.util.stream.Collectors; 11 | 12 | import static org.leibnizcenter.nfa.demo.ParkingMeter.PayState.*; 13 | 14 | public class ParkingMeter { 15 | /** 16 | * How much money is in the machine 17 | */ 18 | private int cents; 19 | 20 | public static void main(String... args) { 21 | // Run example 22 | new ParkingMeter().run(); 23 | } 24 | 25 | private void run() { 26 | // Say we can buy parking for 100 cents 27 | 28 | // Define some actions 29 | CoinDrop drop25cents = new CoinDrop(25); 30 | CoinDrop drop50cents = new CoinDrop(50); 31 | 32 | // Define our NFA 33 | NFA nfa = new NFA.Builder() 34 | .addTransition(PAID_0, drop25cents, PAID_25) 35 | .addTransition(PAID_0, drop50cents, PAID_50) 36 | .addTransition(PAID_25, drop25cents, PAID_50) 37 | .addTransition(PAID_25, drop50cents, PAID_75) 38 | .addTransition(PAID_50, drop25cents, PAID_75) 39 | .addTransition(PAID_50, drop50cents, PAID_0) 40 | .addTransition(PAID_75, drop25cents, PAID_0) 41 | .addTransition(PAID_75, drop50cents, PAID_0) // Paid too much... no money back! 42 | .build(); 43 | 44 | // Apply action step-by-step 45 | Collection endStates1 = nfa.start(PAID_0) 46 | .andThen(drop25cents) 47 | .andThen(drop50cents) 48 | .andThen(drop50cents) 49 | .andThen(drop25cents) 50 | .getState() 51 | .collect(Collectors.toList()); 52 | 53 | // 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) 54 | Collection endStates2 = nfa.apply(PAID_0, new LinkedList<>(Arrays.asList(drop50cents, drop25cents, drop50cents, drop25cents))) 55 | .collect(Collectors.toList()); 56 | 57 | System.out.println("Today earnings: ¢" + cents + "."); 58 | } 59 | 60 | enum PayState implements State { 61 | PAID_0(0), PAID_25(25), PAID_50(50), PAID_75(75); 62 | public final int centsValue; 63 | 64 | PayState(int centsValue) { 65 | this.centsValue = centsValue; 66 | } 67 | } 68 | 69 | private class CoinDrop implements Event { 70 | final int centsValue; 71 | 72 | CoinDrop(int value) { 73 | this.centsValue = value; 74 | } 75 | 76 | @Override 77 | public void accept(PayState from, PayState to) { 78 | System.out.println("Bleep Bloop. Added ¢" + centsValue + " to ¢" + from.centsValue + ". "); 79 | if (to.centsValue <= 0 || to.centsValue >= 100) { 80 | System.out.println("You may park. Good day."); 81 | } else { 82 | System.out.println("You have paid ¢" + to.centsValue + " in total. Please add ¢" + (100 - to.centsValue) + " before you may park."); 83 | } 84 | System.out.println("----------------------------------------------"); 85 | cents += this.centsValue; 86 | } 87 | 88 | @Override 89 | public String toString() { 90 | return "¢" + centsValue; 91 | } 92 | } 93 | } -------------------------------------------------------------------------------- /src/test/java/org/leibnizcenter/nfa/NFATest.java: -------------------------------------------------------------------------------- 1 | package org.leibnizcenter.nfa; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | import com.google.common.collect.Lists; 5 | import com.google.common.collect.Sets; 6 | import org.junit.Test; 7 | 8 | import java.util.HashSet; 9 | import java.util.LinkedList; 10 | import java.util.List; 11 | import java.util.Map; 12 | 13 | import static org.junit.Assert.assertEquals; 14 | import static org.leibnizcenter.nfa.TEvents.eventA; 15 | import static org.leibnizcenter.nfa.TEvents.eventB; 16 | import static org.leibnizcenter.nfa.TStates.S0; 17 | import static org.leibnizcenter.nfa.TStates.S1; 18 | 19 | /** 20 | * Created by maarten on 16-6-16. 21 | */ 22 | public class NFATest { 23 | 24 | public static final Transition TRANSITION_S0_A_S0 = new Transition<>(S0, eventA, S0); 25 | public static final Transition TRANSITION_S1_A_S1 = new Transition<>(S1, eventA, S1); 26 | public static final Transition TRANSITION_S0_A_S1 = new Transition<>(S0, eventA, S1); 27 | 28 | @Test 29 | public void getPathsForInput() { 30 | NFA.Builder b = new NFA.Builder<>(); 31 | 32 | b.addTransition(S0, eventA, S0); 33 | b.addTransition(S1, eventA, S1); 34 | b.addTransition(S0, eventA, S1); 35 | final NFA nfa = b.build(); 36 | 37 | final LinkedList events = new LinkedList<>(Lists.newLinkedList(Lists.newArrayList( 38 | eventA, 39 | eventA, 40 | eventA, 41 | eventA, 42 | eventA, 43 | eventA, 44 | eventA, 45 | eventA, 46 | eventA, 47 | eventA, 48 | eventA, 49 | eventA, 50 | eventA 51 | ))); 52 | List> events1 = ImmutableList.copyOf(events); 53 | Map, PossibleStateTransitionPaths>> possiblePaths = nfa.precomputePaths(events); 54 | 55 | // for (Map.Entry, PossibleBranches> entry : possiblePaths.get(S0).entrySet()) { 56 | // System.out.println(entry.getKey()); 57 | // System.out.println(events1); 58 | // System.out.println(events1.equals(entry.getKey())); 59 | // System.out.println(entry.getKey().equals(events1)); 60 | // } 61 | 62 | final PossibleStateTransitionPaths transitions = possiblePaths.get(S0).get(events1); 63 | assertEquals(transitions.numberOfBranches(), 14); 64 | final int transitionNumber = 104; 65 | assertEquals(transitions.size(), transitionNumber); 66 | final int[] i = {0}; 67 | 68 | transitions.parallelStream().forEach(ignored -> i[0]++); 69 | assertEquals(i[0], transitionNumber); 70 | i[0] = 0; 71 | transitions.forEach(ignored -> i[0]++); 72 | assertEquals(i[0], transitionNumber); 73 | i[0] = 0; 74 | for (Transition ignored : transitions) i[0]++; 75 | assertEquals(i[0], transitionNumber); 76 | } 77 | 78 | @Test 79 | public void getTransitions() { 80 | NFA.Builder b = new NFA.Builder<>(); 81 | 82 | b.addTransition(TRANSITION_S0_A_S0); 83 | b.addTransition(TRANSITION_S1_A_S1); 84 | b.addTransition(TRANSITION_S0_A_S1); 85 | final NFA nfa = b.build(); 86 | 87 | //noinspection unchecked 88 | assertEquals(new HashSet<>(nfa.getTransitions(S0, eventA)), Sets.newHashSet(new Transition<>(S0, eventA, S0), new Transition<>(S0, eventA, S1))); 89 | assertEquals(new HashSet<>(nfa.getTransitions(S0, eventB)), Sets.newHashSet()); 90 | assertEquals(new HashSet<>(nfa.getTransitions(S1, eventB)), Sets.newHashSet()); 91 | } 92 | 93 | 94 | } -------------------------------------------------------------------------------- /src/test/java/org/leibnizcenter/nfa/ParkingMeterTest.java: -------------------------------------------------------------------------------- 1 | package org.leibnizcenter.nfa; 2 | 3 | import org.junit.Test; 4 | 5 | import java.util.Arrays; 6 | import java.util.Collection; 7 | import java.util.Collections; 8 | import java.util.LinkedList; 9 | import java.util.stream.Collectors; 10 | 11 | import static org.junit.Assert.assertEquals; 12 | import static org.leibnizcenter.nfa.ParkingMeterTest.PayState.*; 13 | 14 | /** 15 | * Parking meter example 16 | *

17 | * Created by maarten on 17-6-16. 18 | */ 19 | public class ParkingMeterTest { 20 | /** 21 | * How much money is in the machine 22 | */ 23 | private int cents; 24 | 25 | @Test 26 | public void parkingMeterExample() { 27 | // Say we can buy parking for 100 cents 28 | 29 | // Define some actions 30 | CoinDrop drop25cents = new CoinDrop(25); 31 | CoinDrop drop50cents = new CoinDrop(50); 32 | 33 | // Define our NFA 34 | NFA nfa = new NFA.Builder() 35 | .addTransition(PAYED_0, drop25cents, PAYED_25) 36 | .addTransition(PAYED_0, drop50cents, PAYED_50) 37 | .addTransition(PAYED_25, drop25cents, PAYED_50) 38 | .addTransition(PAYED_25, drop50cents, PAYED_75) 39 | .addTransition(PAYED_50, drop25cents, PAYED_75) 40 | .addTransition(PAYED_50, drop50cents, PAYED_0) 41 | .addTransition(PAYED_75, drop25cents, PAYED_0) 42 | .addTransition(PAYED_75, drop50cents, PAYED_0) // Payed too much... no money back! 43 | .build(); 44 | 45 | Collection endStates0 = manualDroppings(drop25cents, drop50cents, nfa); 46 | 47 | // Apply action step-by-step 48 | Collection endStates1 = nfa.start(PAYED_0) 49 | .andThen(drop25cents) 50 | .andThen(drop50cents) 51 | .andThen(drop50cents) 52 | .andThen(drop25cents) 53 | .getState().collect(Collectors.toList()); 54 | 55 | // Or apply actions in bulk 56 | Collection endStates2 = nfa.apply(PAYED_0, new LinkedList<>(Arrays.asList(drop50cents, drop25cents, drop50cents, drop25cents))) 57 | .collect(Collectors.toList()); 58 | 59 | System.out.println("Today earnings: ¢" + cents + "."); 60 | 61 | assertEquals(endStates1, endStates2); 62 | assertEquals(endStates0, endStates2); 63 | assertEquals(endStates1, Collections.singletonList(PAYED_25)); 64 | } 65 | 66 | private Collection manualDroppings(CoinDrop drop25cents, CoinDrop drop50cents, NFA nfa) { 67 | return nfa.getTransitions(PAYED_0, drop25cents).stream() 68 | .peek(transition -> drop25cents.accept(transition.getFrom(), transition.getTo())) 69 | .flatMap(trans -> nfa.getTransitions(trans.getTo(), drop50cents).stream()) 70 | .peek(transition -> drop50cents.accept(transition.getFrom(), transition.getTo())) 71 | .flatMap(trans -> nfa.getTransitions(trans.getTo(), drop50cents).stream()) 72 | .peek(transition -> drop50cents.accept(transition.getFrom(), transition.getTo())) 73 | .flatMap(trans -> nfa.getTransitions(trans.getTo(), drop25cents).stream()) 74 | .peek(transition -> drop25cents.accept(transition.getFrom(), transition.getTo())) 75 | .map(Transition::getTo).collect(Collectors.toList()); 76 | } 77 | 78 | enum PayState implements State { 79 | PAYED_0(0), PAYED_25(25), PAYED_50(50), PAYED_75(75); 80 | public final int centsValue; 81 | 82 | PayState(int centsValue) { 83 | this.centsValue = centsValue; 84 | } 85 | } 86 | 87 | private class CoinDrop implements Event { 88 | final int centsValue; 89 | 90 | CoinDrop(int value) { 91 | this.centsValue = value; 92 | } 93 | 94 | @Override 95 | public void accept(PayState from, PayState to) { 96 | System.out.println("Bleep Bloop. Added ¢" + centsValue + " to ¢" + from.centsValue + ". "); 97 | if (to.centsValue <= 0 || to.centsValue >= 100) System.out.println("You may park. Good day."); 98 | else 99 | System.out.println("You have paid ¢" + to.centsValue + " in total. Please add ¢" + (100 - to.centsValue) + " before you may park."); 100 | System.out.println("----------------------------------------------"); 101 | cents += this.centsValue; 102 | } 103 | 104 | @Override 105 | public String toString() { 106 | return "¢" + centsValue; 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | org.leibnizcenter 8 | nfa 9 | 1.0.0 10 | jar 11 | 12 | 13 | 14 | MIT License 15 | http://www.opensource.org/licenses/mit-license.php 16 | 17 | 18 | 19 | 20 | 21 | Maarten Trompper 22 | maarten.trompper@gmail.com 23 | Leibniz Center for Law 24 | http://www.leibnizcenter.org/ 25 | 26 | 27 | 28 | ${project.groupId}:${project.artifactId} 29 | Streaming non-deterministic finite automata 30 | https://github.com/digitalheir/java-nfa 31 | 32 | 33 | scm:git:git@github.com:digitalheir/java-nfa 34 | scm:git:git@github.com:digitalheir/java-nfa.git 35 | git@github.com:digitalheir/java-nfa.git 36 | 37 | 38 | 39 | 40 | junit 41 | junit 42 | 4.13.2 43 | test 44 | 45 | 46 | com.google.guava 47 | guava 48 | 31.1-jre 49 | 50 | 51 | 52 | com.github.krukow 53 | clj-ds 54 | 0.0.4 55 | 56 | 57 | org.jetbrains 58 | annotations 59 | 22.0.0 60 | 61 | 62 | 63 | 64 | ossrh 65 | https://oss.sonatype.org/content/repositories/snapshots 66 | 67 | 68 | ossrh 69 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 70 | 71 | 72 | 73 | 74 | 75 | org.apache.maven.plugins 76 | maven-compiler-plugin 77 | 78 | 1.8 79 | 1.8 80 | 81 | 3.3 82 | 83 | 84 | org.apache.maven.plugins 85 | maven-source-plugin 86 | 2.4 87 | 88 | 89 | 90 | 91 | 92 | attach-sources 93 | 94 | jar 95 | 96 | 97 | 98 | 99 | 100 | org.apache.maven.plugins 101 | maven-javadoc-plugin 102 | 2.10.3 103 | 104 | 105 | attach-javadocs 106 | 107 | jar 108 | 109 | 110 | 111 | 112 | -Xdoclint:none 113 | 114 | 115 | 116 | org.sonatype.plugins 117 | nexus-staging-maven-plugin 118 | 1.6.7 119 | true 120 | 121 | ossrh 122 | https://oss.sonatype.org/ 123 | true 124 | 125 | 126 | 127 | org.apache.maven.plugins 128 | maven-gpg-plugin 129 | 1.6 130 | 131 | 132 | sign-artifacts 133 | verify 134 | 135 | sign 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | UTF-8 145 | UTF-8 146 | 147 | 148 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Nondeterministic finite state automata 2 | [![Build Status](https://travis-ci.org/cacfd3a/java-nfa.svg?branch=master)](https://travis-ci.org/cacfd3a/java-nfa) 3 | [![GitHub version](https://badge.fury.io/gh/cacfd3a%2Fjava-nfa.svg)](https://github.com/cacfd3a/java-nfa/releases) 4 | [![License](https://img.shields.io/npm/l/probabilistic-earley-parser.svg)](https://github.com/cacfd3a/java-nfa/blob/master/LICENSE.txt) 5 | 6 | This is a library that provides an implemention of [nondeterminstic finite state automata](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. 7 | 8 | ## Usage 9 | Download [the latest JAR](https://github.com/cacfd3a/nfa/releases/latest) or grab from Maven: 10 | 11 | ```xml 12 | 13 | 14 | org.leibnizcenter 15 | nfa 16 | 1.0.0 17 | 18 | 19 | ``` 20 | 21 | or Gradle: 22 | ```groovy 23 | compile 'org.leibnizcenter:nfa:1.0.0' 24 | ``` 25 | 26 | ## Why? 27 | There are already a bunch of libraries out there which work with deterministic finite state automata (DFAs), 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. 28 | 29 | So why not just use DFAs? Two reasons: 30 | * Determinizing an NFA has an exponential blowup in the number of states. 31 | * 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). 32 | 33 | 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. 34 | 35 | ## Current features 36 | * 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. 37 | * 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*|). 38 | * 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. 39 | 40 | ## Example 41 | Here is a simple example of a parking meter that takes money: 42 | 43 | ```java 44 | public class ParkingMeter { 45 | /** 46 | * How much money is in the machine 47 | */ 48 | private int ¢¢¢; 49 | 50 | public static void main(String[] args) { 51 | // Run example 52 | new ParkingMeter().run(); 53 | } 54 | 55 | public void run() { 56 | // Say we can buy parking for 100 cents 57 | 58 | // Define some actions 59 | CoinDrop drop25cents = new CoinDrop(25); 60 | CoinDrop drop50cents = new CoinDrop(50); 61 | 62 | // Define our NFA 63 | NFA nfa = new NFA.Builder() 64 | .addTransition(PAYED_0, drop25cents, PAYED_25) 65 | .addTransition(PAYED_0, drop50cents, PAYED_50) 66 | .addTransition(PAYED_25, drop25cents, PAYED_50) 67 | .addTransition(PAYED_25, drop50cents, PAYED_75) 68 | .addTransition(PAYED_50, drop25cents, PAYED_75) 69 | .addTransition(PAYED_50, drop50cents, PAYED_0) 70 | .addTransition(PAYED_75, drop25cents, PAYED_0) 71 | .addTransition(PAYED_75, drop50cents, PAYED_0) // Payed too much... no money back! 72 | .build(); 73 | 74 | // Apply action step-by-step 75 | Collection endStates1 = nfa.start(PAYED_0) 76 | .andThen(drop25cents) 77 | .andThen(drop50cents) 78 | .andThen(drop50cents) 79 | .andThen(drop25cents) 80 | .getState().collect(Collectors.toList()); 81 | 82 | // 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) 83 | Collection endStates2 = nfa.apply(PAYED_0, new LinkedList<>(Arrays.asList(drop50cents, drop25cents, drop50cents, drop25cents))) 84 | .collect(Collectors.toList()); 85 | 86 | System.out.println("Today earnings: ¢" + ¢¢¢ + "."); 87 | } 88 | 89 | private class CoinDrop implements Event { 90 | final int ¢; 91 | 92 | CoinDrop(int value) { 93 | this.¢ = value; 94 | } 95 | 96 | @Override 97 | public void accept(PayState from, PayState to) { 98 | System.out.println("Bleep Bloop. Added ¢" + ¢ + " to ¢" + from.¢ + ". "); 99 | if (to.¢ <= 0 || to.¢ >= 100) System.out.println("You may park. Good day."); 100 | else 101 | System.out.println("You have paid ¢" + to.¢ + " in total. Please add ¢" + (100 - to.¢) + " before you may park."); 102 | System.out.println("----------------------------------------------"); 103 | ¢¢¢ += this.¢; 104 | } 105 | 106 | @Override 107 | public String toString() { 108 | return "¢" + ¢; 109 | } 110 | } 111 | 112 | enum PayState implements State { 113 | PAYED_0(0), PAYED_25(25), PAYED_50(50), PAYED_75(75); 114 | public int ¢; 115 | 116 | PayState(int ¢) { 117 | this.¢ = ¢; 118 | } 119 | } 120 | } 121 | ``` 122 | -------------------------------------------------------------------------------- /src/main/java/org/leibnizcenter/nfa/NFA.java: -------------------------------------------------------------------------------- 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 | import java.util.stream.Collectors; 8 | import java.util.stream.Stream; 9 | 10 | /** 11 | * Immutable NFA 12 | *

13 | * Created by maarten on 15-6-16. 14 | */ 15 | @SuppressWarnings("WeakerAccess") 16 | public class NFA> { 17 | public final Map>> transitions; 18 | public final Set states; 19 | public final Multimap statesThatAllowEvent; 20 | 21 | private NFA(Builder builder) { 22 | this.states = ImmutableSet.copyOf(builder.states); 23 | 24 | ImmutableMap.Builder>> immTransitions = new ImmutableMap.Builder<>(); 25 | 26 | // O(transitions.numberOfBranches()) 27 | builder.transitions.forEach((state, eventToTransitionMap) -> { 28 | final ImmutableMultimap.Builder> eventToTransitionMapBuilder = new ImmutableMultimap.Builder<>(); 29 | eventToTransitionMap.forEach(eventToTransitionMapBuilder::putAll); 30 | immTransitions.put(state, eventToTransitionMapBuilder.build()); 31 | }); 32 | this.transitions = immTransitions.build(); 33 | 34 | // O(transitions.numberOfBranches()) 35 | ImmutableMultimap.Builder immStatesThatAllowEvent = new ImmutableMultimap.Builder<>(); 36 | builder.transitions.forEach((state, eventMap) -> 37 | eventMap.forEach((event, transitions) -> 38 | immStatesThatAllowEvent.put(event, state) 39 | ) 40 | ); 41 | statesThatAllowEvent = immStatesThatAllowEvent.build(); 42 | 43 | // 44 | // Sanity check: 45 | // 46 | 47 | // O(transitions.numberOfBranches()) 48 | builder.transitions.forEach((state, eventMap) -> { 49 | assert (eventMap.values().stream() 50 | .flatMap(Collection::stream) 51 | .map(Transition::getFrom) 52 | .filter(st4t3 -> !state.equals(st4t3)) 53 | .limit(1).count() == 0) : "Error in map from state " + state + " to transitions. This is a bug."; 54 | } 55 | ); 56 | } 57 | 58 | @SuppressWarnings("unused") 59 | public PossibleStateTransitionPaths getTransitions(S start, LinkedList events) { 60 | final List events1 = ImmutableList.copyOf(events); // O(n) 61 | return precomputePaths(events) 62 | .get(start) 63 | .get(events1); 64 | } 65 | 66 | public Stream apply(S start, LinkedList events) { 67 | final PossibleStateTransitionPaths transitions = getTransitions(start, events); 68 | return transitions.applyRecursive(); 69 | } 70 | 71 | /** 72 | * O(path.numberOfBranches() * states.numberOfBranches() * transitions.numberOfBranches()) 73 | * 74 | * @param event Input events to use for computing all possible paths along the NFA 75 | * @return A map from starting states to a map of input events to an enumeration of possible branches 76 | */ 77 | public Map, PossibleStateTransitionPaths>> precomputePaths(LinkedList event) { 78 | PersistentList postFixPath = com.github.krukow.clj_lang.PersistentList.create((Iterable) new ArrayList(0)); // O(1) 79 | Map, PossibleStateTransitionPaths>> precomputedPaths = new HashMap<>(states.size());// O(1) 80 | 81 | while (event.size() > 0) { // O(path.numberOfBranches()) * 82 | E lastEvent = event.removeLast(); // O(1) 83 | 84 | postFixPath = postFixPath.plus(lastEvent); // O(1) 85 | 86 | // TODO filter only those states that *can* be reached through the previous action 87 | for (S state : statesThatAllowEvent.get(lastEvent)) { // O(states.numberOfBranches()) * 88 | Map, PossibleStateTransitionPaths> pathsForEvents = precomputedPaths.getOrDefault(state, new HashMap<>());//O(1) 89 | final Collection> possibleTransitions = getTransitions(state, lastEvent); // O(1) 90 | 91 | PossibleStateTransitionPaths possibleBranches; 92 | if (postFixPath.size() == 1) { 93 | possibleBranches = new PossibleStateTransitionPaths<>(state, possibleTransitions, postFixPath, null); // O(1) 94 | } else { 95 | final PersistentList furtherEvents = postFixPath.minus(); 96 | ImmutableMap.Builder> restPaths = ImmutableMap.builder(); // O(1) 97 | possibleTransitions.stream() // O(possibleTransitions.numberOfBranches()) 98 | .map(Transition::getTo) 99 | .distinct() 100 | .forEach(possibleTargetState -> { 101 | PossibleStateTransitionPaths restBranches = precomputedPaths.get(possibleTargetState).get(furtherEvents); 102 | assert restBranches != null : "Possible branches must have been calculated for state " + possibleTargetState; 103 | restPaths.put(possibleTargetState, restBranches); 104 | }); 105 | possibleBranches = new PossibleStateTransitionPaths<>( 106 | state, 107 | possibleTransitions, 108 | postFixPath, 109 | restPaths.build() 110 | ); // O(possibleTransitions.numberOfBranches()) 111 | } 112 | assert !pathsForEvents.containsKey(postFixPath) : "Already computed possible paths for " + postFixPath + "?!"; 113 | pathsForEvents.put(postFixPath, possibleBranches); // O(1) 114 | precomputedPaths.putIfAbsent(state, pathsForEvents);// O(1) 115 | } 116 | } 117 | return precomputedPaths; 118 | } 119 | 120 | public Collection> getTransitions(S from, E event) { 121 | final Multimap> eventTransitionMultimap = transitions.get(from); 122 | if (eventTransitionMultimap != null) return eventTransitionMultimap.get(event); 123 | else return Collections.emptySet(); 124 | } 125 | 126 | public Set getStates() { 127 | return states; 128 | } 129 | 130 | public StateContainer start(S state) { 131 | return new StateContainer(Collections.singletonList(state)); 132 | } 133 | 134 | @SuppressWarnings("unused") 135 | public Collection getStatesThatAllowEvent(E e) { 136 | return statesThatAllowEvent.get(e); 137 | } 138 | 139 | public static class Builder> { 140 | private final Set states; 141 | private final Map>>> transitions; 142 | 143 | public Builder() { 144 | this.states = new HashSet<>(50); 145 | transitions = new HashMap<>(50); 146 | } 147 | 148 | @SuppressWarnings("unused") 149 | public Builder addStates(Collection states) { 150 | this.states.addAll(states); 151 | return this; 152 | } 153 | 154 | @SuppressWarnings("UnusedReturnValue") 155 | public Builder addState(S states) { 156 | this.states.add(states); 157 | return this; 158 | } 159 | 160 | /** 161 | * Will automatically add states if they've not been added separately. 162 | * 163 | * @param from From state 164 | * @param event Transition event 165 | * @param to To state 166 | * @return This builder 167 | */ 168 | public Builder addTransition(S from, E event, S to) { 169 | states.add(from); 170 | states.add(to); 171 | 172 | addTransition(new Transition<>(event, from, to), from, event); 173 | 174 | return this; 175 | } 176 | 177 | /** 178 | * Will automatically add states if they've not been added separately. 179 | * 180 | * @param transitionsToAdd List of transitions. Implicit states will be added if necessary. 181 | * @return This builder 182 | */ 183 | @SuppressWarnings("UnusedReturnValue") 184 | public Builder addTransitions(Collection> transitionsToAdd) { 185 | transitionsToAdd.forEach(this::addTransition); 186 | return this; 187 | } 188 | 189 | /** 190 | * Will automatically add states if they've not been added separately. 191 | * 192 | * @param transition Implicit states will be added if necessary. 193 | * @return This builder 194 | */ 195 | @SuppressWarnings("UnusedReturnValue") 196 | public Builder addTransition(Transition transition) { 197 | S from = transition.from; 198 | S to = transition.to; 199 | E event = transition.event; 200 | states.add(from); 201 | states.add(to); 202 | addTransition(transition, from, event); 203 | return this; 204 | } 205 | 206 | private void addTransition(Transition transition, S from, E event) { 207 | Map>> eventsForState = transitions.getOrDefault(from, new HashMap<>()); 208 | Set> transitionsForEvent = eventsForState.getOrDefault(event, new HashSet<>()); 209 | transitionsForEvent.add(transition); 210 | eventsForState.putIfAbsent(event, transitionsForEvent); 211 | transitions.putIfAbsent(from, eventsForState); 212 | } 213 | 214 | 215 | public NFA build() { 216 | return new NFA<>(this); 217 | } 218 | } 219 | 220 | public class StateContainer { 221 | public Collection states; 222 | 223 | public StateContainer(Collection ses) { 224 | states = ses; 225 | } 226 | 227 | public StateContainer andThen(E e) { 228 | states = states.stream() 229 | .flatMap(from -> getTransitions(from, e).stream() 230 | .peek(transition -> e.accept(transition.getFrom(), transition.getTo())) 231 | ) 232 | .map(Transition::getTo) 233 | .collect(Collectors.toList()); 234 | return this; 235 | } 236 | 237 | public Stream getState() { 238 | return states.stream(); 239 | } 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /src/main/java/org/leibnizcenter/nfa/PossibleStateTransitionPaths.java: -------------------------------------------------------------------------------- 1 | package org.leibnizcenter.nfa; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | import org.leibnizcenter.nfa.util.Pair; 5 | 6 | import java.util.*; 7 | import java.util.function.Consumer; 8 | import java.util.stream.Collectors; 9 | import java.util.stream.Stream; 10 | import java.util.stream.StreamSupport; 11 | 12 | /** 13 | * A collection of all possible paths of state transitions, starting from a given state and a given input token 14 | * Created by maarten on 17-6-16. 15 | */ 16 | @SuppressWarnings("WeakerAccess") 17 | public class PossibleStateTransitionPaths> implements Collection> { 18 | public final List path; 19 | public final Collection> possibleTransitions; 20 | public final Map> furtherPaths; 21 | public final int numberOfBranches; 22 | public final int numberOfTransitions; 23 | public final E e; 24 | public final S from; 25 | 26 | /** 27 | * This constructor has a complexity of O(possibleTransitions.size()) because it has to count the number of paths diverging 28 | * 29 | * @param possibleTransitions Collection of possible transition from a fixed state through a fixed event 30 | * @param path Further string events, including the one that spawns possibleTransitions at the head 31 | * @param furtherPaths Further possible paths from result states of possibleTransitions along the events of the tail of path 32 | */ 33 | public PossibleStateTransitionPaths(S from, Collection> possibleTransitions, List path, Map> furtherPaths) { 34 | this.path = path; 35 | this.possibleTransitions = possibleTransitions; 36 | this.furtherPaths = furtherPaths; 37 | this.e = path.get(0); 38 | this.from = from; 39 | 40 | // TODO don't run the sanity checks because they add complexity 41 | sanityChecks(possibleTransitions, path, furtherPaths); 42 | 43 | int furthBranchNumber = 0; 44 | int numberOfTransitions = possibleTransitions.size(); 45 | if (furtherPaths != null) for (Transition transition : possibleTransitions) { 46 | furthBranchNumber += furtherPaths.get(transition.getTo()).numberOfBranches(); 47 | numberOfTransitions += furtherPaths.get(transition.getTo()).size(); 48 | } 49 | else { 50 | furthBranchNumber += possibleTransitions.size(); 51 | } 52 | this.numberOfBranches = furthBranchNumber; 53 | this.numberOfTransitions = numberOfTransitions; 54 | // System.out.println(this.numberOfTransitions); 55 | } 56 | 57 | public int numberOfBranches() { 58 | return numberOfBranches; 59 | } 60 | 61 | public void sanityChecks(Collection> possibleTransitions, List path, Map> furtherPaths) { 62 | if (possibleTransitions.stream().map(Transition::getFrom).collect(Collectors.toSet()).size() != 1) 63 | throw new Error(); 64 | if (possibleTransitions.stream().map(Transition::getEvent).collect(Collectors.toSet()).size() != 1) 65 | throw new Error(); 66 | if (path.size() > 1) { 67 | if (furtherPaths == null) throw new NullPointerException(); 68 | } else if (furtherPaths != null) throw new NullPointerException(); 69 | 70 | if (furtherPaths != null) { 71 | possibleTransitions.forEach(transition -> { 72 | final PossibleStateTransitionPaths possibleFurtherBranches = furtherPaths.get(transition.getTo()); 73 | if (possibleFurtherBranches.path.size() != path.size() - 1) throw new Error(); 74 | possibleFurtherBranches.possibleTransitions.forEach(t -> { 75 | if (!t.getFrom().equals(transition.getTo())) throw new Error(); 76 | } 77 | ); 78 | } 79 | ); 80 | } 81 | 82 | // possibleTransitions should be unique 83 | assert possibleTransitions.stream().distinct().count() == possibleTransitions.size(); 84 | } 85 | 86 | public int size() { 87 | return numberOfTransitions; 88 | } 89 | 90 | @Override 91 | public boolean isEmpty() { 92 | return numberOfTransitions == 0; 93 | } 94 | 95 | /** 96 | * Runs in O(n), for n is the length of the path 97 | * 98 | * @param o Possible input path. Must be instance of {@see Iterable}. 99 | * @return Whether the given branch is contained in this one 100 | */ 101 | @SuppressWarnings("rawtypes") 102 | @Override 103 | public boolean contains(Object o) { 104 | if (o instanceof Iterable) { 105 | if (o instanceof Collection && ((Collection) o).size() != size()) 106 | return false; 107 | 108 | Iterable iterable = ((Iterable) o); 109 | Iterator itThat = iterable.iterator(); 110 | Iterator> itThis = iterator(); 111 | while (itThat.hasNext() && itThis.hasNext()) 112 | if (!itThis.next().equals(itThat.next())) 113 | return false; 114 | return itThis.hasNext() == itThat.hasNext(); 115 | } 116 | return false; 117 | } 118 | 119 | @NotNull 120 | @Override 121 | public Iterator> iterator() { 122 | return StreamSupport.stream(spliterator(), false) 123 | .iterator(); 124 | } 125 | 126 | // @NotNull 127 | @SuppressWarnings("unchecked") 128 | @Override 129 | public Transition[] toArray() { 130 | Transition[] arr = new Transition[numberOfTransitions]; 131 | Iterator> iterator = iterator(); 132 | for (int i = 0; i < numberOfTransitions; i++) arr[i] = iterator.next(); 133 | return arr; 134 | } 135 | 136 | // @NotNull 137 | @Override 138 | public T[] toArray(T[] a) { 139 | //if (!(Transition.class.isInstance(new Class()))) throw new InvalidParameterException(); 140 | Iterator> iterator = iterator(); 141 | for (int i = 0; i < a.length; i++) { 142 | //noinspection unchecked 143 | a[i] = (T) iterator.next(); 144 | if (!iterator.hasNext()) break; 145 | } 146 | return a; 147 | } 148 | 149 | @Override 150 | public boolean add(Transition transition) { 151 | throw new UnsupportedOperationException(); 152 | } 153 | 154 | @Override 155 | public boolean remove(Object o) { 156 | throw new UnsupportedOperationException(); 157 | } 158 | 159 | @Override 160 | public boolean containsAll(@NotNull Collection c) { 161 | return c.stream() 162 | .filter(this::contains) 163 | .count() == c.size(); 164 | } 165 | 166 | @Override 167 | public boolean addAll(@NotNull Collection> c) { 168 | throw new UnsupportedOperationException(); 169 | } 170 | 171 | @Override 172 | public boolean removeAll(@NotNull Collection c) { 173 | throw new UnsupportedOperationException(); 174 | } 175 | 176 | @Override 177 | public boolean retainAll(@NotNull Collection c) { 178 | throw new UnsupportedOperationException(); 179 | } 180 | 181 | @Override 182 | public void clear() { 183 | throw new UnsupportedOperationException(); 184 | } 185 | 186 | @Override 187 | public Spliterator> spliterator() { 188 | return new BranchesSpliterator<>(this); 189 | } 190 | 191 | public Stream applyRecursive() { 192 | return possibleTransitions.stream().flatMap(t -> { 193 | t.getEvent().accept(t.getFrom(), t.getTo()); 194 | if (furtherPaths == null) return Stream.of(t.getTo()); 195 | else return furtherPaths.get(t.getTo()).applyRecursive(); 196 | }); 197 | } 198 | 199 | private static class BranchesSpliterator> implements Spliterator> { 200 | private final LinkedList, Iterator>>> iteratorState; 201 | 202 | public BranchesSpliterator(PossibleStateTransitionPaths transitionz) { 203 | this.iteratorState = new LinkedList<>(); 204 | iteratorState.add(new Pair<>(transitionz, transitionz.possibleTransitions.iterator())); 205 | } 206 | 207 | public BranchesSpliterator(Pair, Iterator>> state) { 208 | this.iteratorState = new LinkedList<>(); 209 | iteratorState.add(state); 210 | } 211 | 212 | @Override 213 | public boolean tryAdvance(Consumer> action) { 214 | synchronized (iteratorState) { 215 | if (iteratorState.size() <= 0) return false; 216 | final Iterator> iteratorToUse = iteratorState.peek().getValue(); 217 | Transition transition = iteratorToUse.next(); 218 | 219 | if (!iteratorToUse.hasNext()) { 220 | // Exhausted this iteratorState: go on to possible next states 221 | final PossibleStateTransitionPaths rip = iteratorState.pop().getKey(); 222 | if (rip.furtherPaths != null) rip.furtherPaths.forEach((_state, branches) -> 223 | iteratorState.push(new Pair<>(branches, branches.possibleTransitions.iterator())) 224 | ); 225 | } 226 | action.accept(transition); 227 | } 228 | return true; 229 | } 230 | 231 | @Override 232 | public void forEachRemaining(Consumer> action) { 233 | //noinspection StatementWithEmptyBody 234 | while (tryAdvance(action)) { 235 | } 236 | } 237 | 238 | 239 | @Override 240 | public Spliterator> trySplit() { // O(1) 241 | synchronized (iteratorState) { 242 | if (iteratorState.size() > 1) 243 | return new BranchesSpliterator<>(iteratorState.removeLast()); 244 | else return null; 245 | } 246 | } 247 | 248 | @Override 249 | public long estimateSize() { 250 | return iteratorState.stream() 251 | .map(Pair::getKey) 252 | .mapToInt(PossibleStateTransitionPaths::size) 253 | .sum(); 254 | } 255 | 256 | @Override 257 | public long getExactSizeIfKnown() { 258 | return estimateSize(); 259 | } 260 | 261 | @Override 262 | public int characteristics() { 263 | return SIZED | SUBSIZED 264 | | NONNULL 265 | | IMMUTABLE; 266 | // |ORDERED 267 | // |DISTINCT // NOTE: only if we include the state history... 268 | // |SORTED 269 | // |CONCURRENT 270 | } 271 | } 272 | } --------------------------------------------------------------------------------