Разбор выражений на основе отражения - PullRequest
0 голосов
/ 15 апреля 2020

Мне нужно выполнить функции для вычисления выражений, аналогично Excel, но в PHP (OOP). (Этот вопрос, вероятно, является арахисом для всех, кто обладает знанием синтаксического анализатора языка.)

Основной механизм анализирует функции и их требования (отражение) и должен запускать соответствующие функции и в оптимальной последовательности, чтобы:

  • Разрешение неизвестных значений
  • При установке нового значения обновите все зависимые значения

Сценарий

a = b * c
b = c + d
c = 5
d = 8
e = f * 2
f = b + 3 + a

Получить любое вычисленное значение

  • Разрешение a требует разрешения b .
  • Разрешение e требует разрешения f (что, в свою очередь, требует разрешения a и b ).

Установить известное значение

Если c изменяется, функции должны запускаться для пересчета значений.

  • [a = b * c] следует удерживать, поскольку это основываться на переданном значении b.
  • [b = c + d] должен выполняться, поскольку нет неизвестных зависимостей.
  • Любые функции зависят t до b теперь должно также работать, чтобы можно было запустить [a = b * c].
  • Обновив a функция [f = b + 3 + a] должна теперь запустить.
  • Аналогично, теперь должна запускаться функция e .

Вопрос

Мой вопрос касается алгоритм для разрешения и установки значений.

  • Несмотря на то, что он кажется предметом рекурсивного разбора, он более разумно перебирает массив функций и предъявляет любые неразрешенные требования к end?

Например: [Обновление C] не должно запускаться в исходном порядке [a = ..], [b = ..], но [b =], [a =].

  • Следует ли использовать SET logi c уже при разрешении значений, и если это так, не сделает ли это анализ поиска значений выше избыточного? После инициализации класса будут установлены константы и будут выполняться зависимые функции.

Пример рекурсивного исключения:

[Update C] =>
  [Update B] =>
    [Update A] =>
      [Update F] =>
        [Update E] =>

Еще раз, если этот порядок выполнения будет вызываться рекурсивно или аналогично приведенному выше , перебирая плоский массив.

Благодарен за любые ссылки или принципы в этом вопросе.

Ответы [ 2 ]

2 голосов
/ 15 апреля 2020

Это не код ОО, а скорее подтверждение концепции (и мне было скучно).

Код в основном разбит на две половины. Во-первых, это код, который эффективно маркирует строки, а затем создает список зависимостей для каждой переменной. Таким образом, a зависит от b и c et c.

Вторая часть представляет собой систему грубой силы, которая просматривает каждый список зависимостей и проверяет, все ли зависимости разрешимы, каждый раз, когда она может ее решить. (все зависимые поля известны), он добавляет последовательность в список переменных, чтобы сказать, в каком порядке они могут быть запущены. Он продолжает делать это до тех пор, пока все не будут решены, или он больше не будет продвигаться (что может быть что-то вроде = b и b = a).

Я добавил комментарии в коде, может помочь ...

$equations = [
    "a = b * c",
    "b = c + d",
    "c = 5",
    "d = 8",
    "e = f * 2",
    "f = b + 3 + a",
];

$variables = [];
$toBeSolved = [];
$symbols = ["=", "*", "+", "-", "/"];
$evalSeq = 0;
// Extract dependencies and known values
foreach ( $equations as $equation ) {
    $parts = explode ( " ", $equation);
    $equationVars = [];
    $leftVar = array_shift($parts);
    $variables[$leftVar] = null;
    foreach ( $parts as $part ) {
        if ( !in_array ($part, $symbols) && !is_numeric($part) )  {
            $variables[$part] = null;
            $equationVars[] = $part;
        }
    }
    // If there are dependant values, add to list of things to be solved
    if ( count($equationVars) > 0 ) {
        $toBeSolved[$leftVar] = $equationVars;
    }
    // No dependants, so just log sequence as solved
    else    {
        $variables[$leftVar] = $evalSeq++;
    }
}

// Find equations solvable, carry on whilst unknowns left
while ( count($toBeSolved) > 0 ){
    $progress = 0;
    foreach ( $toBeSolved as $var => $dep )   {
        $known = true;
        foreach ( $dep as $variable )   {
            // Flag an unknown variable, so can't solve this yet
            if ( $variables[$variable] === null )    {
                $known = false;
                break;
            }
        }
        // If all values are known, then can be evaluated, so log sequence and remove
        if ( $known )   {
            $variables[$var] = $evalSeq++;
            unset ($toBeSolved[$var]);
            $progress++;
        }
    }
    // If no progress has been made, then can't reduce further.
    if ( $progress == 0 ){
        break;
    }
}
print_r($variables);
// If $toBeSolved is not empty, then can't solve
print_r($toBeSolved);

Конечный результат - что-то вроде ...

Array
(
    [a] => 3
    [b] => 2
    [c] => 0
    [d] => 1
    [e] => 5
    [f] => 4
)

, показывающий, что порядок вычисления равен c, d, b, a, f, e.

Возможно, что вместо сохранения последовательности вы можете сохранить значения вместо этого, так как каждый проход сделан, вы можете вычислить результат уравнения и сохранить его вместе с именем переменной.

0 голосов
/ 17 апреля 2020

Похоже, вопрос состоит из двух отдельных. Цель все еще не слишком ясна, но я постараюсь ответить.

Первое - ваш синтаксис допускает "прямые ссылки". Этот код является точным примером этого:

e = f * 2
f = b + 3 + a

Объявление переменной e ссылается на объект, объявленный позже (f) в его теле, поэтому вы определенно не сможете разрешить его во время одного анализа ваш исходный код Наиболее близкое поведение можно найти в языке JavaScript, где подъем работает аналогичным образом. Насколько я знаю, для реализации этого времени выполнения этот код проходит дважды. Сначала он собирает все объявления, существующие в текущей области видимости. И во время второго прохода он может найти соответствующий узел объявления, на который ссылается исполняемая функция. Конечно, это упрощение, но я надеюсь, что общая идея ясна.

Вторая проблема заключается в том, что порядок выполнения не определен. Или можно сказать, что он неявно определяется зависимостями объявленных функций. Поэтому, если вы хотите рассчитать порядок выполнения на основе этих зависимостей, вы можете реализовать канонический алгоритм топологическая сортировка . Вы должны быть осторожны со ссылками на циклы, потому что этот алгоритм не сможет найти правильную последовательность в этом случае.

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

...