Лучший алгоритм для оценки математического выражения?

Какой лучший алгоритм для оценки математического выражения? Я хотел бы иметь возможность оптимизировать это немного в том смысле, что у меня может быть одна формула с различными переменными, и мне может понадобиться оценить сотни раз, используя разные переменные. Поэтому в принципе, если я могу изначально разобрать формулу так, чтобы она каким-то образом оптимизировалась, и я могу передавать переменные этой оптимизированной версии столько раз, сколько мне нужно, каждый раз, когда она производит для меня результат.

Я напишу это в Delphi или С#. Я уже написал что-то подобное, используя алгоритм маневрового двора, но каждый раз, когда мне нужно вычислять ту же формулу, мне нужно пройти стадию разбора. Должен быть лучший способ сделать это.

Ответы

Ответ 1

Если вы хотите сделать это с помощью Delphi, вы можете посмотреть, как работает блок JclExprEval, часть JEDI Code Library. Я написал это несколько лет назад (это немного переработано); он анализирует функции и переменные и может передать вам указатель метода, который быстро оценивает выражение. Передайте переменные по ссылке, и вы можете изменить их напрямую, и пересчитанное выражение будет рассчитываться соответствующим образом.

В любом случае, основы того, как это работает, могут быть полезны для вас. Рекурсивный спуск синтаксического анализа выражений прост, и, построив дерево, вы можете оценивать несколько раз без повторного разбора. JclExprEval фактически генерирует код для простой стековой машины, так что он может работать немного быстрее, чем интерпретация дерева; стековые машины в значительной степени ограничивают свои операции с памятью массивами и используют переключатели для кодов операций, в то время как интерпретация дерева следует за ссылками по всей куче и часто использует виртуальную отправку (или двойную отправку) для кодов операций, поэтому они обычно заканчиваются медленнее.

Принимая тот же подход, что и JclExprEval при синтаксическом анализе, но написанный на С#, а создание Expression, как предполагает Марк, - это еще один совершенно правильный подход. Скомпилированное выражение JIT должно быть довольно быстро, чем интерпретируемая программа выражения или дерево, которые сами по себе намного быстрее, чем синтаксический анализ.

Ответ 2

В С# с .NET 3.5 вы можете использовать Expression для этого; вы можете создать параметризованное выражение, а затем скомпилировать его для делегата. Это именно то, что я сделал для математического аспекта "Лингвистика" . У меня все еще есть код разбора, который я использовал, если вы этого хотите...

Основной трюк, который я использовал, заключалось в том, что для того, чтобы сохранить тип делегата известным, я использовал массив как тип ввода - обрабатывал разные аргументы как arr [0], arr 1, arr [2] и т.д. Это означает, что я мог бы скомпилировать (например) a Func<decimal[], decimal> (принимает массив decimal s, возвращает a decimal).

Как только вы вызвали Compile(), это очень важно, как если бы у вас был код, чтобы сделать это напрямую.

(редактировать)

В качестве краткого примера использования Expression таким образом (с жестко запрограммированной функцией), см. ниже. Парсер, который я уже написал, в настоящее время работает как проверка предикатов - например, чтобы проверить, что "? + (2 *? -?) = 22 +?" - но нетрудно изменить его, чтобы вместо этого вернуть результат (и ввести больше операций, например sin/pow/etc - предположительно, путем непосредственного сопоставления их с общедоступными методами на вспомогательном объекте (через Expression.Call)).

using System;
using System.Linq.Expressions;
static class Program
{
    static void Main()
    {
        var args = Expression.Parameter(typeof(float[]), "args");
        var x = Expression.ArrayIndex(args, Expression.Constant(0));
        var y = Expression.ArrayIndex(args, Expression.Constant(1));
        var add = Expression.Add(x, y);
        var lambda = Expression.Lambda<Func<float[], float>>(add, args);

        Func<float[], float> func = lambda.Compile();
        Console.WriteLine(func.Call(1, 2));
        Console.WriteLine(func.Call(3, 4));
        Console.WriteLine(func.Call(5, 6));
    }

    static T Call<T>(this Func<T[], T> func, params T[] args)
    { // just allows "params" usage...
        return func(args);
    } 
}