using System.Text.RegularExpressions;
public class RegexSimplifier
public static string Optimize(string regex, bool optimizeForLength)
regex = RemoveRedundantParentheses(regex);
regex = SimplifyNestedQuantifiers(regex, optimizeForLength);
regex = MergeConsecutiveCharacters(regex, optimizeForLength);
regex = CombineStarQuantifiers(regex, optimizeForLength);
regex = OptimizeRepeatedQuantifiers(regex, optimizeForLength);
} while (regex != previous);
private static string RemoveRedundantParentheses(string input)
return Regex.Replace(input, @"\(([01]+)\)", match =>
string inner = match.Groups[1].Value;
return inner.Contains("*") || inner.Contains("|") ? match.Value : inner;
private static string SimplifyNestedQuantifiers(string input, bool optimizeForLength)
return Regex.Replace(input, @"\((.*?)\*\)\*", match =>
string inner = match.Groups[1].Value;
string replacement = $"{inner}*";
return ShouldReplace(match.Value, replacement, optimizeForLength) ? replacement : match.Value;
private static string MergeConsecutiveCharacters(string input, bool optimizeForLength)
input = Regex.Replace(input, @"([0])\1{2,}|([1])\2{2,}", match =>
int count = match.Length;
string replacement = $"{c}{{{count}}}";
return ShouldReplace(match.Value, replacement, optimizeForLength) ? replacement : match.Value;
input = Regex.Replace(input, @"(?<char>[01])(\k<char>+)\(\k<char>\*\)", match =>
char c = match.Groups["char"].Value[0];
int min = match.Groups[1].Length + 1;
string replacement = $"{c}{{{min},}}";
return ShouldReplace(match.Value, replacement, optimizeForLength) ? replacement : match.Value;
private static string CombineStarQuantifiers(string input, bool optimizeForLength)
return Regex.Replace(input, @"(\w+\*)\1+", match =>
string replacement = match.Groups[1].Value;
return ShouldReplace(match.Value, replacement, optimizeForLength) ? replacement : match.Value;
private static string OptimizeRepeatedQuantifiers(string input, bool optimizeForLength)
return Regex.Replace(input, @"(\(.*?\)\*)\1", match =>
string replacement = match.Groups[1].Value;
return ShouldReplace(match.Value, replacement, optimizeForLength) ? replacement : match.Value;
private static bool ShouldReplace(string original, string replacement, bool optimizeForLength)
return !optimizeForLength || replacement.Length <= original.Length;
public class RegexChecker
private string NumberConvertion(int number, int dimension)
if (dimension < 2 || dimension > 36)
throw new ArgumentOutOfRangeException(nameof(dimension), "only from 2 to 36.");
StringBuilder result = new StringBuilder();
bool isNegative = number < 0;
number = Math.Abs(number);
int remainder = number % dimension;
char c = (remainder < 10) ? (char)('0' + remainder) : (char)('A' + (remainder - 10));
char[] chars = result.ToString().ToCharArray();
return new string(chars);
public bool Check(string regex, int division, int dimension = 2, bool ignoreTests = true, bool showTests = false,
bool showOnlyBadResults = false, int operationCount = 10000)
for (int i = 0; i < operationCount && (result || showTests); i++)
int number = Random.Shared.Next();
string convertedNumber = NumberConvertion(number, dimension);
bool regexResult = Regex.IsMatch(convertedNumber, regex, RegexOptions.IgnoreCase);
result = result && (regexResult == (number % division == 0));
if (regexResult != (number % division == 0))
Console.WriteLine($"{number}, {convertedNumber}, {number % division == 0}, {regexResult}");
Console.WriteLine($"{number}, {convertedNumber}, {number % division == 0}, {regexResult}");
Console.WriteLine($"{regex}, {division}, {result}");
private static readonly RegexChecker _regexChecker = new RegexChecker();
public static void Main(string[] args)
string regex = "^(0|1(((0(00)*011)*0(00)*1((1)*00(00)*1)*(1)*00(00)*011)*((0(00)*011)*0(00)*1((1)*00(00)*1)*(1)*01|(0(00)*011)*1)1)*((0(00)*011)*0(00)*1((1)*00(00)*1)*(1)*00(00)*011)*((0(00)*011)*0(00)*1((1)*00(00)*1)*(1)*01|(0(00)*011)*1)0|1(((0(00)*011)*0(00)*1((1)*00(00)*1)*(1)*00(00)*011)*((0(00)*011)*0(00)*1((1)*00(00)*1)*(1)*01|(0(00)*011)*1)1)*((0(00)*011)*0(00)*1((1)*00(00)*1)*(1)*00(00)*011)*(0(00)*011)*0(00)*010|1(((0(00)*011)*0(00)*1((1)*00(00)*1)*(1)*00(00)*011)*((0(00)*011)*0(00)*1((1)*00(00)*1)*(1)*01|(0(00)*011)*1)1)*((0(00)*011)*0(00)*1((1)*00(00)*1)*(1)*00(00)*011)*(0(00)*011)*0(00)*1((1)*00(00)*1)*(1)*00(00)*010)+$";
_regexChecker.Check(regex, number, 2, true, false, false, 10000);
string a = RegexSimplifier.Optimize(regex, true);
string b = RegexSimplifier.Optimize(regex, false);
foreach (string ex in examples)
Console.WriteLine($"Original: {ex}");
Console.WriteLine($"Optimized (length): {RegexSimplifier.Optimize(ex, true)}");
Console.WriteLine($"Optimized (no length): {RegexSimplifier.Optimize(ex, false)}");
_regexChecker.Check(a, number, 2, true, false, false, 10000);
_regexChecker.Check(b, number, 2, true, false, false, 10000);
Console.WriteLine (a.Length);
Console.WriteLine (b.Length);