add: nfa2dfa算法

This commit is contained in:
jackfiled 2024-07-27 14:56:28 +08:00
parent a8886bec33
commit 02c5690d97
9 changed files with 402 additions and 56 deletions

View File

@ -7,7 +7,6 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Folder Include="Abstractions\" />
<None Include="../.gitignore" /> <None Include="../.gitignore" />
<None Include="../.editorconfig" /> <None Include="../.editorconfig" />
</ItemGroup> </ItemGroup>

View File

@ -0,0 +1,91 @@
namespace CanonSharp.Common.LexicalAnalyzer;
public class DeterministicState : IEquatable<DeterministicState>
{
public Guid Id { get; } = Guid.NewGuid();
public Dictionary<char, DeterministicState> Transaction { get; } = [];
public bool Equals(DeterministicState? other) => other is not null && Id.Equals(other.Id);
public override bool Equals(object? obj) => obj is DeterministicState other && Equals(other);
public override int GetHashCode() => Id.GetHashCode();
}
public class DeterministicFiniteAutomation
{
public DeterministicState Start { get; }
public HashSet<DeterministicState> FinalStates { get; }
private DeterministicFiniteAutomation(DeterministicState start, HashSet<DeterministicState> finalStates)
{
Start = start;
FinalStates = finalStates;
}
private record Pair(HashSet<NondeterministicState> States, DeterministicState State);
public static DeterministicFiniteAutomation Create(NondeterministicFiniteAutomation nfa)
{
Dictionary<NondeterministicStateSet, DeterministicState> map = [];
HashSet<DeterministicState> visited = [];
Queue<Pair> queue = [];
HashSet<DeterministicState> finalStates = [];
HashSet<NondeterministicState> startClosure = nfa.Start.CalculateEmptyClosure();
DeterministicState start = new();
map.Add(new NondeterministicStateSet(startClosure), start);
queue.Enqueue(new Pair(startClosure, start));
while (queue.TryDequeue(out Pair? pair))
{
if (pair.States.Any(s => nfa.FinalStates.Contains(s)))
{
finalStates.Add(pair.State);
}
Dictionary<char, HashSet<NondeterministicState>> next = [];
foreach (NondeterministicState state in pair.States)
{
foreach (KeyValuePair<EmptyChar,HashSet<NondeterministicState>> transaction in
state.Transactions.Where(p => !p.Key.IsEmpty))
{
HashSet<NondeterministicState> closure = [];
foreach (NondeterministicState s in transaction.Value)
{
closure.UnionWith(s.CalculateEmptyClosure());
}
if (next.TryGetValue(transaction.Key.Char, out HashSet<NondeterministicState>? n))
{
n.UnionWith(closure);
}
next.Add(transaction.Key.Char, closure);
}
}
foreach (KeyValuePair<char,HashSet<NondeterministicState>> transaction in next)
{
NondeterministicStateSet set = new(transaction.Value);
if (!map.TryGetValue(set, out DeterministicState? nextState))
{
nextState = new DeterministicState();
map.Add(set, nextState);
}
pair.State.Transaction.Add(transaction.Key, nextState);
if (visited.Add(nextState))
{
queue.Enqueue(new Pair(transaction.Value, nextState));
}
}
}
return new DeterministicFiniteAutomation(start, finalStates);
}
}

View File

@ -0,0 +1,49 @@
namespace CanonSharp.Common.LexicalAnalyzer;
public class EmptyChar : IEquatable<EmptyChar>
{
public bool IsEmpty { get; }
public char Char { get; }
public static EmptyChar Empty => new();
private EmptyChar()
{
IsEmpty = true;
Char = char.MaxValue;
}
public EmptyChar(char c)
{
IsEmpty = false;
Char = c;
}
public bool Equals(EmptyChar? other)
{
if (other is null)
{
return false;
}
if (IsEmpty)
{
return other.IsEmpty;
}
return Char == other.Char;
}
public override bool Equals(object? obj) => obj is EmptyChar other && Equals(other);
public override int GetHashCode()
{
return IsEmpty.GetHashCode() ^ Char.GetHashCode();
}
public override string ToString()
{
return IsEmpty ? "ε" : Char.ToString();
}
}

View File

@ -0,0 +1,76 @@
namespace CanonSharp.Common.LexicalAnalyzer;
public class NondeterministicState : IEquatable<NondeterministicState>
{
public Guid Id { get; } = Guid.NewGuid();
public Dictionary<EmptyChar, HashSet<NondeterministicState>> Transactions { get; } = [];
public bool Equals(NondeterministicState? other) => other is not null && Id.Equals(other.Id);
public void AddTransaction(EmptyChar c, NondeterministicState state)
{
if (Transactions.TryGetValue(c, out HashSet<NondeterministicState>? states))
{
states.Add(state);
}
else
{
Transactions.Add(c, [state]);
}
}
public override bool Equals(object? obj) => obj is NondeterministicState other && Equals(other);
public override int GetHashCode() => Id.GetHashCode();
public HashSet<NondeterministicState> CalculateEmptyClosure()
{
HashSet<NondeterministicState> result = [];
Queue<NondeterministicState> queue = [];
queue.Enqueue(this);
while (queue.TryDequeue(out NondeterministicState? state))
{
result.Add(state);
if (!state.Transactions.TryGetValue(EmptyChar.Empty, out HashSet<NondeterministicState>? next))
{
continue;
}
foreach (NondeterministicState s in next.Where(s => !result.Contains(s)))
{
queue.Enqueue(s);
}
}
return result;
}
}
public class NondeterministicStateSet(HashSet<NondeterministicState> states) : IEquatable<NondeterministicStateSet>
{
private readonly HashSet<NondeterministicState> _states = states;
public bool Equals(NondeterministicStateSet? other)
{
if (other is null)
{
return false;
}
return _states.Count == other._states.Count && _states.All(s => other._states.Contains(s));
}
public override bool Equals(object? obj) => obj is NondeterministicStateSet other && Equals(other);
public override int GetHashCode() => _states.Aggregate(0, (current, state) => current ^ state.GetHashCode());
}
public class NondeterministicFiniteAutomation(NondeterministicState start, HashSet<NondeterministicState> finalStates)
{
public NondeterministicState Start { get; } = start;
public HashSet<NondeterministicState> FinalStates { get; } = finalStates;
}

View File

@ -0,0 +1,109 @@
namespace CanonSharp.Common.LexicalAnalyzer;
public abstract class RegularExpression
{
public abstract NondeterministicFiniteAutomation Convert2Nfa();
public static RegularExpression Empty => new EmptyExpression();
public static RegularExpression Single(char c) => new SymbolExpression(c);
public static RegularExpression Alternate(RegularExpression left, RegularExpression right) =>
new AlternationExpression(left, right);
public static RegularExpression Concatenate(RegularExpression first, RegularExpression second) =>
new ConcatenationExpression(first, second);
public static RegularExpression Kleene(RegularExpression inner) => new KleeneExpression(inner);
}
public class EmptyExpression : RegularExpression
{
public override NondeterministicFiniteAutomation Convert2Nfa()
{
NondeterministicState final = new();
NondeterministicState start = new();
start.AddTransaction(EmptyChar.Empty, final);
return new NondeterministicFiniteAutomation(start, [final]);
}
}
public class SymbolExpression(char symbol) : RegularExpression
{
public char Symbol { get; } = symbol;
public override NondeterministicFiniteAutomation Convert2Nfa()
{
NondeterministicState final = new();
NondeterministicState start = new();
start.AddTransaction(new EmptyChar(Symbol), final);
return new NondeterministicFiniteAutomation(start, [final]);
}
}
public class AlternationExpression(RegularExpression left, RegularExpression right) : RegularExpression
{
public RegularExpression Left { get; } = left;
public RegularExpression Right { get; } = right;
public override NondeterministicFiniteAutomation Convert2Nfa()
{
NondeterministicFiniteAutomation left = Left.Convert2Nfa();
NondeterministicFiniteAutomation right = Right.Convert2Nfa();
NondeterministicState final = new();
foreach (NondeterministicState state in left.FinalStates.Concat(right.FinalStates))
{
state.AddTransaction(EmptyChar.Empty, final);
}
NondeterministicState start = new();
start.AddTransaction(EmptyChar.Empty, left.Start);
start.AddTransaction(EmptyChar.Empty, right.Start);
return new NondeterministicFiniteAutomation(start, [final]);
}
}
public class ConcatenationExpression(RegularExpression first, RegularExpression second) : RegularExpression
{
public RegularExpression First { get; } = first;
public RegularExpression Second { get; } = second;
public override NondeterministicFiniteAutomation Convert2Nfa()
{
NondeterministicFiniteAutomation first = First.Convert2Nfa();
NondeterministicFiniteAutomation second = Second.Convert2Nfa();
foreach (NondeterministicState state in first.FinalStates)
{
state.AddTransaction(EmptyChar.Empty, second.Start);
}
return new NondeterministicFiniteAutomation(first.Start, second.FinalStates);
}
}
public class KleeneExpression(RegularExpression inner) : RegularExpression
{
public RegularExpression Inner { get; } = inner;
public override NondeterministicFiniteAutomation Convert2Nfa()
{
NondeterministicFiniteAutomation inner = Inner.Convert2Nfa();
NondeterministicState final = new();
final.AddTransaction(EmptyChar.Empty, inner.Start);
foreach (NondeterministicState state in inner.FinalStates)
{
state.AddTransaction(EmptyChar.Empty, final);
}
return new NondeterministicFiniteAutomation(final, [final]);
}
}

View File

@ -5,9 +5,4 @@
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<Compile Include="LexicalAnalyzer\RegularExpression.fs" />
<Compile Include="LexicalAnalyzer\NondeterministicFiniteAutomation.fs" />
</ItemGroup>
</Project> </Project>

View File

@ -1,38 +0,0 @@
module CanonSharp.Parser.LexicalAnalyzer.NondeterministicFiniteAutomation
open System
open System.Collections.Generic
open CanonSharp.Parser.LexicalAnalyzer.RegularExpression
type NondeterministicState(id: Guid, transaction: Option<char> -> list<NondeterministicState>) =
member val id = id
member val transaction = transaction
override this.GetHashCode() = this.id.GetHashCode()
new() = NondeterministicState(Guid.NewGuid(), fun a -> list.Empty)
type NondeterministicFiniteAutomation(states: HashSet<NondeterministicState>, entryTransaction: Option<char> -> list<NondeterministicState>) =
member val states = states
member val entryTransaction = entryTransaction
let convertEmptyToNonDeterministicFiniteAutomation (expression: EmptyExpression) =
let final = NondeterministicState()
let transaction (a: Option<char>) =
match a with
| Some _ -> list.Empty
| None -> [final]
let states = HashSet()
let _ = states.Add(final)
NondeterministicFiniteAutomation(states, transaction)
let convertToNondeterministicFiniteAutomation expression: RegularExpression =
match expression with
| EmptyExpression ->

View File

@ -1,12 +0,0 @@
module CanonSharp.Parser.LexicalAnalyzer.RegularExpression
type RegularExpression =
| EmptyExpression
| SymbolExpression of symbol: char
| AlternationExpression of left: RegularExpression * right : RegularExpression
| ConcatenationExpression of first: RegularExpression * second : RegularExpression
| KleeneExpression of expression: RegularExpression
let convertToNondeterministicFiniteAutomation expression =
match expression with
| EmptyExpression emptyExpression ->

View File

@ -0,0 +1,77 @@
using CanonSharp.Common.LexicalAnalyzer;
namespace CanonSharp.Tests.LexicalAnalyzerTests;
public class RegularExpressionTests
{
[Fact]
public void KleeneTest()
{
RegularExpression expression = RegularExpression.Concatenate(
RegularExpression.Kleene(RegularExpression.Single('a')),
RegularExpression.Single('b'));
NondeterministicFiniteAutomation automation = expression.Convert2Nfa();
automation.Start.Transactions.TryGetValue(EmptyChar.Empty, out HashSet<NondeterministicState>? next);
Assert.NotNull(next);
Assert.Contains(next, s => s.Transactions.ContainsKey(new EmptyChar('a')));
Assert.Contains(next, s => s.Transactions.ContainsKey(new EmptyChar('b')));
}
[Fact]
public void AlternateTest()
{
RegularExpression expression = RegularExpression.Alternate(
RegularExpression.Kleene(RegularExpression.Single('a')),
RegularExpression.Single('b'));
NondeterministicFiniteAutomation automation = expression.Convert2Nfa();
automation.Start.Transactions.TryGetValue(EmptyChar.Empty, out HashSet<NondeterministicState>? next);
Assert.NotNull(next);
Assert.Contains(next, s => s.Transactions.ContainsKey(new EmptyChar('b')));
NondeterministicState? state = (from item in next
where item.Transactions[EmptyChar.Empty].Count == 2
select item).FirstOrDefault();
Assert.NotNull(state);
Assert.Contains(state.Transactions[EmptyChar.Empty],
s => s.Transactions.ContainsKey(new EmptyChar('a')));
}
[Fact]
public void ConvertTest()
{
RegularExpression expression = RegularExpression.Alternate(
RegularExpression.Kleene(RegularExpression.Single('a')),
RegularExpression.Single('b'));
NondeterministicFiniteAutomation automation = expression.Convert2Nfa();
DeterministicFiniteAutomation dfa = DeterministicFiniteAutomation.Create(automation);
DeterministicState state2 = dfa.Start.Transaction['a'];
Assert.Equal(state2, state2.Transaction['a']);
DeterministicState state3 = dfa.Start.Transaction['b'];
Assert.Empty(state3.Transaction);
Assert.Equal(3, dfa.FinalStates.Count);
}
[Fact]
public void NondeterministicStateSetTest()
{
Dictionary<NondeterministicStateSet, char> map = [];
NondeterministicState key1 = new();
NondeterministicState key2 = new();
map.Add(new NondeterministicStateSet([key1, key2]), 'a');
Assert.Equal('a', map[new NondeterministicStateSet([key2, key1])]);
}
}