...

четверг, 19 ноября 2015 г.

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

Когда-то давно, мне понадобился парсер математических выражений на 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}'");
    }
}


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

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

Комментариев нет:

Отправить комментарий