/// <summary>
        /// Creates the transitions file if it doesn't exist.
        /// </summary>
        private void CreateTransitionsWithMinOffsets(string fileName, Func <IStateMachineBuilder> createBuilder,
                                                     int wordLength)
        {
            var targetPath = Path.Combine(_workingDirectory, fileName);

            if (File.Exists(targetPath))
            {
                return;
            }

            var builder   = createBuilder();
            var compacter = new TransitionCompacter(builder);

            var minOffsets           = GetMinOffsets(builder, compacter, wordLength);
            var minOffsetTransitions = SubtractOffsets(builder, compacter, minOffsets, wordLength);

            var lines = minOffsetTransitions.Select(t => t.ToString(CultureInfo.InvariantCulture));

            File.WriteAllLines(targetPath, lines);

            var offsetPath = Path.Combine(_workingDirectory, $"o_{fileName}");
            var offsets    = compacter.Offsets.Select(t => t.ToString(CultureInfo.InvariantCulture));

            File.WriteAllLines(offsetPath, offsets);

            var minOffsetPath  = Path.Combine(_workingDirectory, $"m_{fileName}");
            var minOffsetLines = minOffsets.Select(t => t.ToString(CultureInfo.InvariantCulture));

            File.WriteAllLines(minOffsetPath, minOffsetLines);
        }
        private static IEnumerable <int> SubtractOffsets(IStateMachineBuilder builder, TransitionCompacter compacter,
                                                         IReadOnlyList <int> minOffsets, int wordLength)
        {
            var alphabet = Enumerable.Range(0, builder.AlphabetSize).ToList();
            var transitionsInCurrentLevel        = new HashSet <int>(builder.EntryStates.Select(e => e * builder.AlphabetSize));
            var compactTransitionsInCurrentLevel =
                new HashSet <int>(builder.EntryStates.Select(e => e * builder.AlphabetSize - compacter.Offsets[e]));
            var transitionsWithMinOffsets = new int[compacter.Transitions.Count];

            Array.Fill(transitionsWithMinOffsets, -1);
            for (var i = 0; i < wordLength; ++i)
            {
                var statesInPreviousLevel        = transitionsInCurrentLevel;
                var compactStatesInPreviousLevel = compactTransitionsInCurrentLevel;
                transitionsInCurrentLevel        = new HashSet <int>();
                compactTransitionsInCurrentLevel = new HashSet <int>();
                var orderedPrevious        = statesInPreviousLevel.OrderBy(x => x).ToList();
                var orderedCompactPrevious = compactStatesInPreviousLevel.OrderBy(x => x).ToList();
                for (var j = 0; j < orderedPrevious.Count; ++j)
                {
                    var previous        = orderedPrevious[j];
                    var compactPrevious = orderedCompactPrevious[j];
                    foreach (var c in alphabet)
                    {
                        if (builder.IsNull(previous + c))
                        {
                            continue;
                        }
                        var n = builder.Transitions[previous + c];
                        var m = compacter.Transitions[compactPrevious + c];
                        transitionsInCurrentLevel.Add(n);
                        compactTransitionsInCurrentLevel.Add(m);

                        transitionsWithMinOffsets[compactPrevious + c] = m - minOffsets[i];
                    }
                }
            }
            return(transitionsWithMinOffsets);
        }
        private static IReadOnlyList <int> GetMinOffsets(IStateMachineBuilder builder, TransitionCompacter compacter,
                                                         int wordLength)
        {
            var alphabet   = Enumerable.Range(0, builder.AlphabetSize).ToList();
            var minOffsets = new int[wordLength];
            var maxOffsets = new int[wordLength];
            var transitionsInCurrentLevel        = new HashSet <int>(builder.EntryStates.Select(e => e * builder.AlphabetSize));
            var compactTransitionsInCurrentLevel =
                new HashSet <int>(builder.EntryStates.Select(e => e * builder.AlphabetSize - compacter.Offsets[e]));

            for (var i = 0; i < wordLength; ++i)
            {
                var transitionsInPreviousLevel        = transitionsInCurrentLevel;
                var compactTransitionsInPreviousLevel = compactTransitionsInCurrentLevel;
                transitionsInCurrentLevel        = new HashSet <int>();
                compactTransitionsInCurrentLevel = new HashSet <int>();
                var orderedPrevious        = transitionsInPreviousLevel.OrderBy(x => x).ToList();
                var orderedCompactPrevious = compactTransitionsInPreviousLevel.OrderBy(x => x).ToList();
                for (var j = 0; j < orderedPrevious.Count; ++j)
                {
                    var previous        = orderedPrevious[j];
                    var compactPrevious = orderedCompactPrevious[j];
                    foreach (var c in alphabet)
                    {
                        if (builder.IsNull(previous + c))
                        {
                            continue;
                        }
                        var n = builder.Transitions[previous + c];
                        var m = compacter.Transitions[compactPrevious + c];
                        transitionsInCurrentLevel.Add(n);
                        compactTransitionsInCurrentLevel.Add(m);
                    }
                }
                minOffsets[i] = compactTransitionsInCurrentLevel.Min();
                maxOffsets[i] = compactTransitionsInCurrentLevel.Max();
            }
            if (maxOffsets.Max() < ushort.MaxValue)
            {
                return(Enumerable.Repeat(0, wordLength).ToList());
            }
            var largestDistance = Enumerable.Range(0, wordLength).Max(i => maxOffsets[i] - minOffsets[i]);

            for (var i = 0; i < wordLength; ++i)
            {
                if (maxOffsets[i] < largestDistance)
                {
                    minOffsets[i] = 0;
                }
            }
            return(minOffsets);
        }