package pl.edu.amu.wmi.daut.base; import java.util.List; import java.util.ArrayList; import java.util.LinkedList; import java.util.HashMap; import java.util.Map; import java.util.HashSet; import java.util.Set; import java.util.Vector; /** * Klasa zwierająca operacje na automatach. */ public class AutomataOperations { /** * Klasa reprezentuje stan C powstały poprzez połączenie stanów A i B w wyniku operacji * intersection. */ private static final class CombinedState { /** * Przypisuje stanowi C jego składowe stany A i B. */ public void set(State a, State b) { qA = a; qB = b; } @Override public String toString() { return "A" + String.valueOf(qA.hashCode()) + "B" + String.valueOf(qB.hashCode()); } public State getB() { return qB; } public State getA() { return qA; } private State qA; private State qB; } /** *Metoda zwraca automat akceptujący odwrócenie języka, * akceptowanego przez dany automat "parent". */ public static AutomatonSpecification reverseLanguageAutomaton( NaiveAutomatonSpecification parentAutomaton) { NaiveAutomatonSpecification childAutomaton = new NaiveAutomatonSpecification(); if (parentAutomaton.isEmpty()) { return childAutomaton; } List<State> parentStates = new ArrayList<State>(); List<State> childStates = new ArrayList<State>(); parentStates.addAll(parentAutomaton.allStates()); //utwórz sztucznie stan początkowy. //bedzie łączony przez epsilon ze stanami końcowymi automatu wejściowego. State initialChildState = childAutomaton.addState(); childStates.add(initialChildState); childAutomaton.markAsInitial(initialChildState); //zadeklaruj tabelkę translacji stanów z automatu wejściowego //na stany z automatu wyjściowego. Map<State, State> parentToSonStates = new HashMap<State, State>(); //krok 1. utwórz stany, oraz zaznacz je jako początkowe lub końcowe. for (State parentState : parentStates) { State childState = childAutomaton.addState(); childStates.add(childState); //dodaj do tabelki translacji stanów parentToSonStates.put(parentState, childState); //jeśli stan jest początkowym, zaznacz go jako końcowy. if (parentState == parentAutomaton.getInitialState()) childAutomaton.markAsFinal(childStates.get(childStates.size() - 1)); //jeśli stan jest końcowym, utwórz połączenie z jedynym możliwym stanem początkowym. else if (parentAutomaton.isFinal(parentState)) { EpsilonTransitionLabel eps = new EpsilonTransitionLabel(); childAutomaton.addTransition(initialChildState, childState, eps); } } //krok 2. utwórz krawędzie. //z każdego stanu w automacie wejściowym... for (State parentState : parentStates) { //pobierz każdą wychodzącą krawędź... for (OutgoingTransition parentTransition : parentAutomaton.allOutgoingTransitions(parentState)) { //pobierz stan wyjściowy z krawędzi State targetState = parentTransition.getTargetState(); //pobierz z tabelki translacji stanów stany: wejściowy i początkowy State childStateFrom = parentToSonStates.get(parentState); State childStateTo = parentToSonStates.get(targetState); //dodaj do listy krawędzi krawędź między stanami w kierunku odwrotnym niż oryginalny childAutomaton.addTransition(childStateTo, childStateFrom, parentTransition.getTransitionLabel()); } } return childAutomaton; } /** * Metoda tworzy przejście od stanu stateC do nowego stanu utworzonego przez parę A i B w * combinedC po etykiecie transition. Dodanie nowo utworzonego stanu stateCn do listy newStates * wraz z wpisaniem jej oraz jej kombinacji stanów do HashMap. * hashMaps - 0 - statesC, 1 - statesCHandle, 2 - combinedStatesC */ private static boolean makeTransition(CombinedState combinedC, List newStates, TransitionLabel transition, List<HashMap> hashMaps, State stateC, AutomatonSpecification automatonC, boolean isFinal) { State stateCn; boolean empty = true; if (hashMaps.get(0).containsValue(combinedC.toString())) stateCn = (State) hashMaps.get(1).get( hashMaps.get(2).get(combinedC.toString()).toString()); else { stateCn = automatonC.addState(); hashMaps.get(2).put(combinedC.toString(), combinedC); hashMaps.get(0).put(stateCn, combinedC.toString()); hashMaps.get(1).put(combinedC.toString(), stateCn); newStates.add(stateCn); empty = false; } automatonC.addTransition(stateC, stateCn, transition); if (isFinal) automatonC.markAsFinal(stateCn); return empty; } /** * Metoda zwracajaca Automat akceptujacy jezyk bedacy dopelnieniem jezyka * akceptowanego przez Automat otrzymywany "na wejsciu". */ static AutomatonSpecification complementLanguageAutomaton(DeterministicAutomatonSpecification automaton, Set<Character> alfabet) { AutomatonSpecification returned = automaton.clone(); returned.makeFull(alfabet.toString()); for (State obecny : returned.allStates()) { if (returned.isFinal(obecny)) returned.unmarkAsFinalState(obecny); else returned.markAsFinal(obecny); } return returned; } /** * Metoda zwracająca automat akceptujący przecięcie języków akceptowanych przez * dwa podane automaty. */ public static AutomatonSpecification intersection( AutomatonSpecification automatonA, AutomatonSpecification automatonB) { boolean empty, isFinal = false; CombinedState combinedC = new CombinedState(); AutomatonSpecification automatonC = new NaiveAutomatonSpecification(); State qA = automatonA.getInitialState(); State qB = automatonB.getInitialState(); State qC = automatonC.addState(); automatonC.markAsInitial(qC); if (automatonA.isFinal(qA) && automatonB.isFinal(qB)) automatonC.markAsFinal(qC); List<OutgoingTransition> lA; List<OutgoingTransition> lB; List<State> lC = new java.util.LinkedList<State>(); List<State> newStates = new java.util.LinkedList<State>(); newStates.add(qC); /* * combinedStatesC - zawiera łańcuch kontrolny odpowiadający kombinacji stanów A i B * statesC - zawiera stan C z łańcuchem kobminacji jego stanów A i B * statesCHandle - zawiera uchwyt do stanu C poprzez łańcuch kontrolny jego kombinacji * stanów A i B */ HashMap<String, CombinedState> combinedStatesC = new HashMap<String, CombinedState>(); HashMap<State, String> statesC = new HashMap<State, String>(); HashMap<String, State> statesCHandle = new HashMap<String, State>(); List<HashMap> hashMaps = new LinkedList<HashMap>(); hashMaps.add(statesC); hashMaps.add(statesCHandle); hashMaps.add(combinedStatesC); combinedC.set(qA, qB); combinedStatesC.put(combinedC.toString(), combinedC); statesC.put(qC, combinedC.toString()); statesCHandle.put(combinedC.toString(), qC); do { lC.addAll(newStates); newStates.clear(); empty = true; for (State stateC : lC) { combinedC = combinedStatesC.get(statesC.get(stateC)); qA = combinedC.getA(); qB = combinedC.getB(); lA = automatonA.allOutgoingTransitions(qA); lB = automatonB.allOutgoingTransitions(qB); for (OutgoingTransition qAn : lA) { for (OutgoingTransition qBn : lB) { TransitionLabel tL = qAn.getTransitionLabel().intersect( qBn.getTransitionLabel()); if (!tL.isEmpty() && !tL.canBeEpsilon()) { combinedC = new CombinedState(); combinedC.set(qAn.getTargetState(), qBn.getTargetState()); if (automatonA.isFinal(qAn.getTargetState()) && automatonB.isFinal(qBn.getTargetState())) isFinal = true; else isFinal = false; if (!makeTransition(combinedC, newStates, tL, hashMaps, stateC, automatonC, isFinal)) empty = false; } } } //Epsilon przejścia for (OutgoingTransition transitionToAn : lA) { if (transitionToAn.getTransitionLabel().canBeEpsilon()) { combinedC = new CombinedState(); combinedC.set(transitionToAn.getTargetState(), qB); if (automatonA.isFinal(transitionToAn.getTargetState()) && automatonB.isFinal(qB)) isFinal = true; else isFinal = false; if (!makeTransition(combinedC, newStates, new EpsilonTransitionLabel(), hashMaps, stateC, automatonC, isFinal)) empty = false; } } for (OutgoingTransition transitionToBn : lB) { if (transitionToBn.getTransitionLabel().canBeEpsilon()) { combinedC = new CombinedState(); combinedC.set(qA, transitionToBn.getTargetState()); if (automatonA.isFinal(qA) && automatonB.isFinal(transitionToBn.getTargetState())) isFinal = true; else isFinal = false; if (!makeTransition(combinedC, newStates, new EpsilonTransitionLabel(), hashMaps, stateC, automatonC, isFinal)) empty = false; } } } lC.clear(); } while (!empty); return automatonC; } /** * Zwraca automat akceptujący domknięcie Kleene'ego * języka akceptowanego przez dany automat. */ public static AutomatonSpecification getKleeneStar(AutomatonSpecification automaton) { AutomatonSpecification kleeneautomaton = new NaiveAutomatonSpecification(); State state1 = kleeneautomaton.addState(); kleeneautomaton.markAsInitial(state1); kleeneautomaton.markAsFinal(state1); if (!automaton.isEmpty()) { State state2 = kleeneautomaton.addState(); kleeneautomaton.addTransition(state1, state2, new EpsilonTransitionLabel()); kleeneautomaton.insert(state2, automaton); for (State state : kleeneautomaton.allStates()) { if (kleeneautomaton.isFinal(state)) { kleeneautomaton.addTransition(state, state1, new EpsilonTransitionLabel()); } } } return kleeneautomaton; } /** * dla automatu z epsilon-przejsciami tworzy rownowazny automat bez epsilon-przejsc. */ public void getRidOfEpsilonTransitions(AutomatonSpecification epsilonAutomaton, AutomatonSpecification resultAutomaton) { List<State> loadedStates = epsilonAutomaton.allStates(); HashMap<State, State> connectedStates = new HashMap<State, State>(); for (State currentState : loadedStates) connectedStates.put(currentState, resultAutomaton.addState()); resultAutomaton.markAsInitial(epsilonAutomaton.getInitialState()); for (State currentState : loadedStates) { if (epsilonAutomaton.isFinal(currentState)) resultAutomaton.markAsFinal(connectedStates.get(currentState)); for (OutgoingTransition transition : epsilonAutomaton.allOutgoingTransitions(currentState)) { TransitionLabel label = transition.getTransitionLabel(); if (!(label.canBeEpsilon())) { epsilonAutomaton.addTransition(connectedStates.get(currentState), connectedStates.get(transition.getTargetState()), transition.getTransitionLabel()); Set<State> epsilonClosure = epsilonAutomaton. getEpsilonClosure(transition.getTargetState()); for (State state : epsilonClosure) resultAutomaton.addTransition(connectedStates.get(currentState), connectedStates.get(state), transition.getTransitionLabel()); } } } } /** * Metoda tworząca automat akceptujący sumę 2 jezyków. */ public static AutomatonSpecification sum( AutomatonSpecification automatonA, AutomatonSpecification automatonB) { AutomatonSpecification automaton = new NaiveAutomatonSpecification(); State q0 = automaton.addState(); State q1 = automaton.addState(); State q2 = automaton.addState(); automaton.markAsInitial(q0); automaton.insert(q1, automatonA); automaton.insert(q2, automatonB); automaton.addTransition(q0, q1, new EpsilonTransitionLabel()); automaton.addTransition(q0, q2, new EpsilonTransitionLabel()); return automaton; } /** * Zwraca automat akceptujący język powstały w wyniku zastosowania homomorfizmu h na * języku akceptowanym przez automat automaton. Homomorfizm jest dany jako mapa, w której * kluczami są znaki, a wartościami - napisy. * @param alphabet alfabet w postaci String, np. abc * @param automaton automat wejściowy * @param h homomorfizm języka */ AutomatonSpecification homomorphism(AutomatonSpecification automaton, Map<Character, String> h, String alphabet) { if (automaton.isEmpty()) { return automaton; } char[] tablica; tablica = alphabet.toCharArray(); AutomatonSpecification homoautomaton = new NaiveDeterministicAutomatonSpecification(); List<State> states = new ArrayList<State>(); states.addAll(automaton.allStates()); HashMap<State, State> connectedStates = new HashMap<State, State>(); for (State current : states) { if (!connectedStates.containsKey(current)) connectedStates.put(current, homoautomaton.addState()); for (OutgoingTransition currenttrans : automaton.allOutgoingTransitions(current)) { TransitionLabel tl = currenttrans.getTransitionLabel(); for (char znak : tablica) { if (tl.canAcceptCharacter(znak)) { String napis = h.get(znak); int dlugosc = napis.length(); char[] znaki = napis.toCharArray(); State docelowy = currenttrans.getTargetState(); State prev = current; if (dlugosc == 0) { homoautomaton.addTransition(prev, docelowy, new EpsilonTransitionLabel()); } for (int i = 0; i < dlugosc - 1; i++) { State next = homoautomaton.addState(); homoautomaton.addTransition(prev, next, new CharTransitionLabel(znaki[i])); prev = next; } homoautomaton.addTransition(prev, docelowy, new CharTransitionLabel(znaki[dlugosc])); connectedStates.put(docelowy, homoautomaton.addState()); } } } } return homoautomaton; } /** * Klasa pomocnicza do determinize2(). Reprezentuje "zbiór stanów" będący stanem automatu dfa. */ private static class PowerSetElement { private Set<State> nfaStates; private State dfaState; private static int numberOfPowerSetElements = 0; public PowerSetElement(Iterable<State> nfaStatesRemote, State dfaStateRemote) { nfaStates = new HashSet<State>(); for (State s : nfaStatesRemote) { nfaStates.add(s); } dfaState = dfaStateRemote; numberOfPowerSetElements++; } /** * Funkcja pomocnicza do determinize2(). Rekurencyjnie tworzy listę stanów * występujących w dfa. Przed użyciem tej funkcji trzeba jednak dodać do listOfStates * jeden element - odpowiadający zbioru pustemu. */ public static void giveAllPowerSetElements(List<PowerSetElement> listOfStates, List<State> dfaStatesRemote, List<State> nfaStatesRemote) { Set<State> nfaStates = new HashSet<State>(); giveAllPowerSetElementsRecursive(listOfStates, dfaStatesRemote, nfaStatesRemote, nfaStates, 0); } private static void giveAllPowerSetElementsRecursive(List<PowerSetElement> listOfStates, List<State> dfaStatesRemote, List<State> nfaStatesRemote, Set<State> nfaStates, int depth) { if (depth < nfaStatesRemote.size()) { //Gałąź dla false(Obecnie rozpatrywany stan NFA nie jest brany) giveAllPowerSetElementsRecursive(listOfStates, dfaStatesRemote, nfaStatesRemote, nfaStates, depth + 1); //Gałąź dla true(Obecnie rozpatrywany stan NFA jest brany) nfaStates.add(nfaStatesRemote.get(depth)); giveAllPowerSetElementsRecursive(listOfStates, dfaStatesRemote, nfaStatesRemote, nfaStates, depth + 1); nfaStates.remove(nfaStatesRemote.get(depth)); } else { listOfStates.add(new PowerSetElement(nfaStates, dfaStatesRemote.get(numberOfPowerSetElements))); } } public Set<State> getnfaStates() { return nfaStates; } public State getdfaState() { return dfaState; } public static void resetNumber() { numberOfPowerSetElements = 0; } }; /** * Metoda pomocnicza dla determinize2(). Tworzy podstawowy zbiór etykiet przejścia używanych * przez oba automaty - niedeterministyczny i deterministyczny. * Na wejściu otrzymuje zbiór T oraz nową "unikalną" etykietę przejścia. * Dla wszystkich etykiet przejścia poza AnyTransitionLabel * i ComplementCharClassTransitionLabel metoda dodaje to T przejścia znakowe * (CharTransitionLabel). Dla tych dwóch zaś tworzy JEDNO przejście * ComplementCharClassTransitionLabel będące przecięciem wszystkich otrzymanych * przejść "dopełnieniowych". * * UWAGA! Metoda nie obsługuje przejścia CharClassTransitionLabel. * * @throws StructureException "Nieznana etykieta przejścia." */ private static void putTransitionLabelInSet(HashSet<TransitionLabel> tSet, TransitionLabel transitionLabel) throws StructureException { //Sprawdzenie, czy etykieta przejścia nie jest pusta. if (!transitionLabel.isEmpty()) //Sprawdzenie, czy jest to AnyTransitionLabel. if (transitionLabel instanceof AnyTransitionLabel) { putTransitionLabelInSet(tSet, new ComplementCharClassTransitionLabel("")); //Sprawdzenie, czy jest to CharTransitionLabel. } else if (transitionLabel instanceof CharTransitionLabel) { tSet.add(transitionLabel); //Sprawdzenie, czy jest to CharSetTransitionLabel. } else if (transitionLabel instanceof CharSetTransitionLabel) { for (char sign : ((CharSetTransitionLabel) transitionLabel).getCharSet()) { tSet.add(new CharTransitionLabel(sign)); for (TransitionLabel t : tSet) { if (t instanceof ComplementCharClassTransitionLabel) { ((ComplementCharClassTransitionLabel) t).getSet().add(sign); } } } //Sprawdzenie, czy jest to CharRangeTransitionLabel. } else if (transitionLabel instanceof CharRangeTransitionLabel) { for (char k = ((CharRangeTransitionLabel) transitionLabel) .getFirstChar(); k <= ((CharRangeTransitionLabel) transitionLabel) .getSecondChar(); k++) { tSet.add(new CharTransitionLabel(k)); } //Sprawdzenie, czy jest to CharClassTransitionLabel. } else if (transitionLabel instanceof CharClassTransitionLabel) { throw new StructureException(); //Sprawdzenie, czy jest to ComplementCharClassTransitionLabel. } else if (transitionLabel instanceof ComplementCharClassTransitionLabel) { ComplementCharClassTransitionLabel newOne = (ComplementCharClassTransitionLabel) transitionLabel; ComplementCharClassTransitionLabel oldOne = null; TransitionLabel resTL = null; Set<Character> newSet = newOne.getSet(); for (TransitionLabel t : tSet) { if (t instanceof ComplementCharClassTransitionLabel) { String oldBuilder = t.toString(); oldOne = new ComplementCharClassTransitionLabel( oldBuilder.substring(2, oldBuilder.length() - 1)); resTL = oldOne.intersect(newOne); Set<Character> excluded = new HashSet<Character>(); excluded.addAll(oldOne.getSet()); excluded.removeAll(newOne.getSet()); for (Character sign : excluded) tSet.add(new CharTransitionLabel(sign)); excluded.clear(); excluded.addAll(newOne.getSet()); excluded.removeAll(oldOne.getSet()); for (Character sign : excluded) tSet.add(new CharTransitionLabel(sign)); oldOne = (ComplementCharClassTransitionLabel) t; newOne = (ComplementCharClassTransitionLabel) resTL; newSet = newOne.getSet(); } else { newSet.add(t.toString().charAt(0)); } } tSet.add(newOne); if (oldOne != null) tSet.remove(oldOne); //Pozostałe są nieobsługiwane i powodują wyrzucenie wyjątku. } else throw new StructureException(); } /** * Metoda determinizuje automat niedeterministyczny bez epsilon-przejść. * Determinizacja przebiega zgodnie z algorytmem przedstawionym na wykładzie. * Automat resultDfa na wejściu powinien być pusty! */ public static void determinize2(AutomatonSpecification nfa, DeterministicAutomatonSpecification resultDfa) throws StructureException { //Sprawdzenie, czy resultDfa na pewno jest pusty. if (resultDfa.isEmpty()) { //Sprawdzenie, czy język akceptowany przez nfa nie jest pusty. //Niestety, przy użyciu metody isNotEmpty wyskakuje WIELKI BRZYDKI BŁĄD. //Coś szwankuje w tamtej metodzie. Gdy będzie naprawione - można zamienić //na zakomentowaną wersję. //if (!(nfa.isNotEmpty())) { if (!nfa.prefixChecker(nfa.getInitialState())) { State one = resultDfa.addState(); resultDfa.markAsInitial(one); return; } //Sprawdzenie, czy nfa nie jest już deterministyczny. if (nfa.isDeterministic()) { resultDfa.fromString(nfa.toString()); return; } //Utworzenie pustego zbioru przejść, oraz dodatkowego zbioru przejść //klasy ComplementCharClassTransitionLabel HashSet<TransitionLabel> tSet = new HashSet<TransitionLabel>(); //Utworzenie listy stanów automatu NFA. Oznaczenie: K. List<State> kList = nfa.allStates(); //Uzupełnienie zbioru przejść, aby był zbiorem przejść automatu NFA. Oznaczenie: T. //Dla każdej unikalnej etykiety przejścia umieszczamy ją w T za pomocą metody //putTransitionLabelInSet() for (State s : kList) { for (OutgoingTransition oT : nfa.allOutgoingTransitions(s)) { if ((oT.getTransitionLabel() instanceof ComplementCharClassTransitionLabel) || (oT.getTransitionLabel() instanceof AnyTransitionLabel)) { putTransitionLabelInSet(tSet, oT.getTransitionLabel()); } else { if (tSet.isEmpty()) { putTransitionLabelInSet(tSet, oT.getTransitionLabel()); } boolean isUnique = true; for (TransitionLabel t : tSet) { if (oT.getTransitionLabel().intersect(t).equals(oT .getTransitionLabel())) { isUnique = false; break; } } if (isUnique) putTransitionLabelInSet(tSet, oT.getTransitionLabel()); } } } //Obliczenie liczby stanów automatu DFA. Oznaczenie: |K'|. int nrOfdfaStates = (int) Math.pow((double) 2, (double) (nfa.countStates())); //Utworzenie stanów automatu DFA. Oznaczenie: K'. for (int i = 0; i < nrOfdfaStates; i++) { resultDfa.addState(); } List<State> kPrimList = resultDfa.allStates(); List<PowerSetElement> kPrimAdditionalList = new Vector<PowerSetElement>(); PowerSetElement.giveAllPowerSetElements(kPrimAdditionalList, kPrimList, kList); //Odnajduje stany końcowe NFA. Vector<Integer> fList = new Vector<Integer>(); for (State seeker : kList) { if (nfa.isFinal(seeker)) { fList.add(kList.indexOf(seeker)); } } //Na ich podstawie oznacza stany końcowe w DFA. for (Integer endState : fList) { for (PowerSetElement structure : kPrimAdditionalList) { if ((!resultDfa.isFinal(structure.getdfaState())) && (structure.getnfaStates().contains(kList.get(endState)))) resultDfa.markAsFinal(structure.getdfaState()); } } //Odnajduje stan początkowy obu automatów. HashSet<State> initialStates = new HashSet<State>(); initialStates.add(nfa.getInitialState()); for (PowerSetElement structure : kPrimAdditionalList) { if (initialStates.equals(structure.getnfaStates())) { resultDfa.markAsInitial(structure.getdfaState()); break; } } //Utworzenie przejść dla nowego automatu na podstawie przejść z nfa. //Dla każdego "nowego stanu w dfa będącego tak naprawdę zbiorem stanów z nfa" for (PowerSetElement structure : kPrimAdditionalList) { if (structure.getnfaStates().isEmpty()) { for (PowerSetElement targetStructure : kPrimAdditionalList) { if (targetStructure.getnfaStates().isEmpty()) resultDfa.addTransition(structure.getdfaState(), targetStructure.getdfaState(), new AnyTransitionLabel()); } } else { for (TransitionLabel t : tSet) { HashSet<State> whereTo = new HashSet<State>(); if (t instanceof ComplementCharClassTransitionLabel) { for (State s : structure.getnfaStates()) { for (OutgoingTransition oT : nfa.allOutgoingTransitions(s)) { if (!(t.intersect(oT.getTransitionLabel()).isEmpty())) { whereTo.add(oT.getTargetState()); } } } } else { for (State s : structure.getnfaStates()) { for (OutgoingTransition oT : nfa.allOutgoingTransitions(s)) { if (oT.getTransitionLabel().canAcceptCharacter(t.toString() .charAt(0))) { whereTo.add(oT.getTargetState()); } } } } PowerSetElement thereGoesNow = structure; for (PowerSetElement targetStructure : kPrimAdditionalList) { if (whereTo.equals(targetStructure.getnfaStates())) { thereGoesNow = targetStructure; break; } } //Gdy skończyliśmy przecinanie wszystkich etykiet przejścia //tworzymy OutgoingTransition. resultDfa.addTransition(structure.getdfaState(), thereGoesNow.getdfaState(), t); } } } PowerSetElement.resetNumber(); //Nie zadziała zmniejszanie automatu, bo nie dostajemy alfabetu "na wejściu". //Można co prawda wygenerować ze zbioru T, ale dla choć jednego wystąpienia //etykiety przejścia ComplementCharClassTransitionLabel generowanie to nie będzie //poprawnie działało. //resultDfa.deleteUselessStates(); } else { throw new StructureException(); } } /** * Metoda tworząca automat akcpetujący konkatenację dwóch języków, * akceptowanych przez dwa dane automaty L i R. */ public static AutomatonSpecification concatenation( final AutomatonSpecification automatonL, final AutomatonSpecification automatonR) { AutomatonSpecification wsa; wsa = automatonL.clone(); List<State> statesL = new ArrayList<State>(); statesL.addAll(wsa.allStates()); for (State state : statesL) { if (wsa.isFinal(state)) { wsa.insert(state, automatonR); wsa.unmarkAsFinalState(state); } } return wsa; } }