Как стать автором
Обновить

Регулярные выражения и математический парсер

Время на прочтение 2 мин
Количество просмотров 18K
Когда-то давно мне понадобился парсер математических выражений на C#. Конечно, скачать готовую реализацию — не проблема. Но вот только Интернета у меня в те годы не было. В итоге абсолютно без раздумий и без теоретических основ парсеров, конечных автоматов и прочего он был написан через регулярные выражения. Минут за 10. Стоит отметить, что нужны были только арифметический действия и скобки. Поддержка тригонометрических функций и прочего не требовалась.

Для начала выделим регулярные выражения для чисел и действий:
private const string RegexBr = "\\(([1234567890\\.\\+\\-\\*\\/^%]*)\\)";    // Скобки
private const string RegexNum = "[-]?\\d+\\.?\\d*";                         // Числа
private const string RegexMulOp = "[\\*\\/^%]";                             // Первоприоритетные числа
private const string RegexAddOp = "[\\+\\-]";                               // Второприоритетные числа

Теперь метод, который полученную строку разделяет на элементы и рекурсивно их вычисляет:
public static double Parse(string str)
{
    // Парсинг скобок
    var matchSk = Regex.Match(str, RegexBr);
    if (matchSk.Groups.Count > 1)
    {
        string inner = matchSk.Groups[0].Value.Substring(1, matchSk.Groups[0].Value.Trim().Length - 2);
        string left = str.Substring(0, matchSk.Index);
        string right = str.Substring(matchSk.Index + matchSk.Length);

        return Parse(left + Parse(inner).ToString(CultureInfo.InvariantCulture) + right);
    }

    // Парсинг действий
    var matchMulOp = Regex.Match(str, string.Format("({0})\\s?({1})\\s?({2})\\s?", RegexNum, RegexMulOp, RegexNum));
    var matchAddOp = Regex.Match(str, string.Format("({0})\\s?({1})\\s?({2})\\s?", RegexNum, RegexAddOp, RegexNum));
    var match = matchMulOp.Groups.Count > 1 ? matchMulOp : matchAddOp.Groups.Count > 1 ? matchAddOp : null;
    if (match != null)
    {
        string left = str.Substring(0, match.Index);
        string right = str.Substring(match.Index + match.Length);
        return Parse(left + ParseAct(match).ToString(CultureInfo.InvariantCulture) + right);
    }

    // Парсинг числа
    try
    {
        return double.Parse(str, CultureInfo.InvariantCulture);
    }
    catch (FormatException)
    {
        throw new FormatException(string.Format("Неверная входная строка '{0}'", str));
    }
}

И последним напишем метод, который непосредственно вычисляет значение действия:
private static double ParseAct(Match match)
{
    double a = double.Parse(match.Groups[1].Value, CultureInfo.InvariantCulture);
    double b = double.Parse(match.Groups[3].Value, CultureInfo.InvariantCulture);

    switch (match.Groups[2].Value)
    {
        case "+": return a + b;
        case "-": return a - b;
        case "*": return a * b;
        case "/": return a / b;
        case "^": return Math.Pow(a, b);
        case "%": return a % b;
        default: throw new FormatException($"Неверная входная строка '{match.Value}'");
    }
}

Такое вот «ненормальное программирование» у меня было. Исходный текст полностью приводить не вижу никакого смысла — вряд ли это кому-нибудь понадобится. Но в любом случае составить класс из трех кусков — дело пары секунд.

Спасибо за внимание.
Теги:
Хабы:
+4
Комментарии 16
Комментарии Комментарии 16

Публикации

Истории

Ближайшие события

Московский туристический хакатон
Дата 23 марта – 7 апреля
Место
Москва Онлайн
Геймтон «DatsEdenSpace» от DatsTeam
Дата 5 – 6 апреля
Время 17:00 – 20:00
Место
Онлайн