/// <summary> /// Returns a new automaton which is the result of intersection with another DFA. /// Both Automaton must be DFA. /// </summary> public Automaton IntersectionWithDFA(Automaton automaton) { // Check they have the same alphabet. if (automaton.Alphabet.Count != this.Alphabet.Count) { throw new InvalidOperationException("Could not calculate the intersection as the alphabets were distinct"); } foreach (var symbol in automaton.Alphabet) { if (!_alphabet.Contains(symbol)) { throw new InvalidOperationException("Could not calculate the intersection as the alphabets were distinct"); } } var newAutomaton = new Automaton(_alphabet); var stateLookup = new Dictionary <(int, int), int>(); // Only one start state per dfa var stateStart = (this.StartStates.First(), automaton.StartStates.First()); var stateQueue = new Queue <(int firstState, int secondState)>(); Func <(int, int), bool, int> AddState = ((int firstState, int secondState)stateTuple, bool isStartState) => { bool isFinalState = false; if (this.FinalStates.Contains(stateTuple.firstState) && automaton.FinalStates.Contains(stateTuple.secondState)) { isFinalState = true; } var stateId = newAutomaton.AddState(isFinalState: isFinalState, isStartState: isStartState); stateLookup[stateTuple] = stateId; stateQueue.Enqueue(stateTuple); return(stateId); }; AddState(stateStart, true); while (stateQueue.TryDequeue(out var stateTuple)) { var fromStateId = stateLookup[stateTuple]; foreach (var symbol in _alphabet) { var reachableStates1 = this.TransitionMatrix.GetStates(stateTuple.firstState, symbol); if (reachableStates1.Count == 0) { continue; } var reachableStates2 = automaton.TransitionMatrix.GetStates(stateTuple.secondState, symbol); if (reachableStates2.Count == 0) { continue; } // DFA, so must only be one more state. var newStateTuple = (reachableStates1.First(), reachableStates2.First()); if (!stateLookup.TryGetValue(newStateTuple, out var stateId)) { stateId = AddState(newStateTuple, false); } newAutomaton.AddTransition(fromStateId, stateId, symbol); } } return(newAutomaton); }
static Automaton() { Canonical = new DataTypes.Automata.Automaton(RegularLanguage.Symbols); var stateIdLookup = new Dictionary <int, int>(); var transitions = new[]
/// <summary> /// Returns a new <see cref="Automaton" /> which is a DFA /// </summary> public Automaton ToDFA() { // A list of states. Each list will become a new state var automaton = new Automaton(_alphabet); var newAutomatonStates = new Dictionary <HashSet <int>, int>(HashSet <int> .CreateSetComparer()); var stateQueue = new Queue <HashSet <int> >(); // Setup subprocedure Func <HashSet <int>, bool, int> AddNewState = (HashSet <int> set, bool isStartState) => { bool isFinalState = false; foreach (var currentState in set) { if (_finalStates.Contains(currentState)) { isFinalState = true; break; } } var currentStateId = automaton.AddState(isStartState: isStartState, isFinalState: isFinalState); stateQueue.Enqueue(set); newAutomatonStates.Add(set, currentStateId); return(currentStateId); }; // Begin DFA calculation var set = new HashSet <int>(_startStates); AddEpsilonStates(set); AddNewState(set, true); while (stateQueue.TryDequeue(out var currentStateList)) { var currentStateId = newAutomatonStates[currentStateList]; foreach (var symbol in Alphabet) { var reachableStates = new HashSet <int>(); foreach (var currentState in currentStateList) { var transitionStates = TransitionMatrix.GetStates(currentState, symbol); foreach (var transitionState in transitionStates) { reachableStates.Add(transitionState); } } if (reachableStates.Count == 0) { // Ignore the empty state continue; } AddEpsilonStates(reachableStates); // We have all reachable states with the given symbol. If a state in the new automata // already exists for this combination, then we don't need to add a new state. if (!newAutomatonStates.TryGetValue(reachableStates, out var toStateId)) { toStateId = AddNewState(reachableStates, false); } automaton.AddTransition(currentStateId, toStateId, symbol); } } return(automaton); }
/// <summary> /// Converts a regex to an automaton /// Based on https://en.wikipedia.org/wiki/Thompson%27s_construction /// </summary> public static Automaton RegexToAutomaton(string regex, char[] alphabet) { // Initialise variables int startState = 0; int finalState = 0; var symbols = new HashSet <char>(alphabet); symbols.Add(Automaton.Epsilon); // Create automaton var automaton = new Automaton(alphabet); // https://medium.com/swlh/visualizing-thompsons-construction-algorithm-for-nfas-step-by-step-f92ef378581b // Convert to postfix var postFixRegex = RegexToPostfix(regex); Stack <(int startState, int finalState)> NFAPartitions = new Stack <(int, int)>(); foreach (var character in postFixRegex) { switch (character) { case KleeneStarOperator: var partition = NFAPartitions.Pop(); startState = automaton.AddState(); finalState = automaton.AddState(); automaton.AddTransition(startState, partition.startState, Automaton.Epsilon); automaton.AddTransition(startState, finalState, Automaton.Epsilon); automaton.AddTransition(partition.finalState, partition.startState, Automaton.Epsilon); automaton.AddTransition(partition.finalState, finalState, Automaton.Epsilon); NFAPartitions.Push((startState, finalState)); break; case ConcatenationOperator: var rightPartition = NFAPartitions.Pop(); var leftPartition = NFAPartitions.Pop(); // Because each partition will always have 0 incoming transitions from their start states, // to merge the states, it is simply a case of copying the transitions from the right partition's // start state to the final state of the left partition, and then finally remove the first state. int copyState = rightPartition.startState; int copiedToState = leftPartition.finalState; foreach (var symbol in symbols) { var states = automaton.TransitionMatrix.GetStates(copyState, symbol); foreach (var state in states) { automaton.AddTransition(copiedToState, state, symbol); } } automaton.DeleteState(copyState, skipIncomingTransitions: true); NFAPartitions.Push((leftPartition.startState, rightPartition.finalState)); break; case UnionOperator: var topPartition = NFAPartitions.Pop(); var bottomPartition = NFAPartitions.Pop(); startState = automaton.AddState(); finalState = automaton.AddState(); automaton.AddTransition(startState, topPartition.startState, Automaton.Epsilon); automaton.AddTransition(startState, bottomPartition.startState, Automaton.Epsilon); automaton.AddTransition(topPartition.finalState, finalState, Automaton.Epsilon); automaton.AddTransition(bottomPartition.finalState, finalState, Automaton.Epsilon); NFAPartitions.Push((startState, finalState)); break; default: // Not an operator. It is a symbol if (!symbols.Contains(character)) { throw new ArgumentException($"Invalid regex. Character {character} not a member of the alphabet"); } startState = automaton.AddState(); finalState = automaton.AddState(); automaton.AddTransition(startState, finalState, character); NFAPartitions.Push((startState, finalState)); break; } } var automataPartition = NFAPartitions.Pop(); automaton.SetAsStartState(automataPartition.startState); automaton.SetAsFinalState(automataPartition.finalState); return(automaton); }