public Dfa EquivalenceMinimization(Dfa dfa) { // Initialize with two groups from terminals and everything else var counter = 0; var groups = new Dictionary <Group <State>, int> { { dfa.Final, counter++ }, { dfa.States.Where(s => !dfa.Final.Contains(s)).ToGroup(), counter++ } }; var marked = new Group <Group <State> >(); var unmarked = new Queue <Group <State> >(groups.Keys); // Continue until all groups are marked while (unmarked.Any()) { var current = unmarked.Dequeue(); if (current.Count == 1) { marked.Add(current); continue; } var table = from state in current let transitions = ( from input in dfa.Inputs let target = dfa.TransitionMap.TryGetValue((state, input), out var transition) ? transition : -1 let targetGroup = target != -1 ? groups.Keys.Single(g => g.Contains(target)) : null let targetGroupId = !(targetGroup is null) ? groups[targetGroup] : -1 select targetGroupId ).ToArray() select new { State = state, Transitions = transitions, Hash = transitions.Aggregate(0, (p, c) => (p, c).GetHashCode()) }; var partitions = from row in table group row by row.Hash into rowsByHash select rowsByHash; var newGroups = partitions .Select(hashGroup => new Group <State>(hashGroup.Select(g => g.State))) .ToList(); if (newGroups.Count == 1) { marked.Add(current); continue; } // Move all marked back to unmarked foreach (var markedGroup in marked) { unmarked.Enqueue(markedGroup); } marked.Clear(); // Group is getting partitioned, so remove it groups.Remove(current); foreach (var newGroup in newGroups) { groups.Add(newGroup, counter++); unmarked.Enqueue(newGroup); } } // Return the original dfa if nothing has been minimized if (!groups.Any(g => g.Key.Count > 1)) { return(dfa); } // Rebuild the dfa from groups var id = 0; var statesFromGroups = groups.Keys .OrderBy(g => g.Min()) .Select(g => (Group: g, Id: id++)) .ToDictionary(kvp => kvp.Group, kvp => kvp.Id); var newStart = statesFromGroups[groups.Keys.Single(g => g.Contains(dfa.Start))]; var newTransitions = ( from sourceGroup in groups.Keys let source = sourceGroup.First() from input in dfa.Inputs where dfa.TransitionMap.ContainsKey((source, input)) let target = dfa.TransitionMap[(source, input)]
public Dfa MinimalDfaFromDfa(Dfa dfa) => RemoveDeadStates(EquivalenceMinimization(dfa));