Разбор арифметического выражения и построение дерева из него в Java - PullRequest
37 голосов
/ 04 января 2011

Мне нужна была помощь в создании пользовательских деревьев с арифметическим выражением.Например, вы вводите это арифметическое выражение:

(5+2)*7

Дерево результатов должно выглядеть следующим образом:

    *
   / \
  +   7
 / \
5   2

У меня есть несколько пользовательских классов для представления различных типов узлов, т.е.PlusOp, LeafInt и т. Д. Мне не нужно оценивать выражение, просто создайте дерево, чтобы позже я мог выполнять с ним другие функции.Кроме того, у отрицательного оператора «-» может быть только один дочерний элемент, и для представления «5-2» вы должны ввести его как 5 + (-2).

Требуется некоторая проверка выражения дляУбедитесь, что каждый тип оператора имеет правильный номер.аргументов / потомков, каждая открывающая скобка сопровождается закрывающей скобкой.

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

Буду признателен за любую помощь.Спасибо:)

(я читал, что вы можете написать грамматику и использовать antlr / JavaCC и т. Д. Для создания дерева разбора, но я не знаком с этими инструментами или с написанием грамматик, так что если это вашерешение, я был бы признателен, если бы вы могли предоставить некоторые полезные учебники / ссылки для них.)

Ответы [ 5 ]

48 голосов
/ 04 января 2011

Предполагая, что это какая-то домашняя работа, и вы хотите сделать это самостоятельно ..

Я сделал это один раз, вам нужен стек

Итак, что вы делаете для примера:

    parse    what to do?                Stack looks like
      (      push it onto the stack     (
      5      push 5                     (, 5
      +      push +                     (, 5, +
      2      push 2                     (, 5, +, 2
      )      evaluate until (           7            
      *      push *                     7, *
      7      push 7                     +7, *, 7
      eof    evaluate until top         49

Символы типа "5" или "+" могут быть просто сохранены как строки или простые объекты, или вы можете сохранить + как объект + () без установки значений и устанавливать их при оценке.

Полагаю, для этого также требуется порядок приоритета, поэтому я опишу, как это работает.

в случае: 5 + 2 * 7

Вы должны нажать 5 push + push 2, следующая операция имеет более высокий приоритет, поэтому вы также нажимаете ее, а затем нажимаете три. Когда вы встречаете либо a), либо конец файла, либо оператор с более низким или равным приоритетом, вы начинаете вычислять стек до предыдущего (или начала файла.

Поскольку ваш стек теперь содержит 5 + 2 * 7, при его оценке вы сначала вставляете 2 * 7 и помещаете получившийся узел * (2,7) в стек, а затем еще раз оцениваете три верхних элемента на стек (5 + * узел), поэтому дерево получается правильным.

Если бы он был заказан другим способом: 5 * 2 + 7, вы будете толкать, пока не попадете в стек с «5 * 2», тогда вы достигнете более низкого приоритета +, что означает оценку того, что у вас есть сейчас. Вы оценили бы 5 * 2 в * узел и нажали его, затем продолжили бы, нажав + и 3, чтобы у вас был * узел + 7, после чего вы оценили бы это.

Это означает, что у вас есть переменная с «наибольшим текущим приоритетом», которая хранит 1, когда вы нажимаете +/-, 2, когда вы нажимаете * или / и 3 ​​для «^». Таким образом, вы можете просто проверить переменную, чтобы увидеть, является ли приоритет вашего следующего оператора <= ваш текущий приоритет. </p>

если ")" считается приоритетом 4, вы можете рассматривать его как другие операторы, за исключением того, что он удаляет соответствующий "(", более низкий приоритет не будет.

12 голосов
/ 05 сентября 2013

Я хотел ответить на ответ Билла К., но мне не хватает репутации, чтобы добавить туда комментарий (это действительно тот ответ, к которому относится).Вы можете думать об этом как о добавлении к ответу Билла К., потому что он был немного неполным.Отсутствует соображение оператор ассоциативности ;а именно, как анализировать выражения типа:

49 / 7 / 7

В зависимости от того, является ли деление ассоциативным или левым, ответом будет:

49 / (7 / 7) => 49 / 1 => 49

или

(49 / 7) / 7 => 7 / 7 => 1

Как правило, деление и вычитание считаются левоассоциативными (т. Е. Случай два выше), тогда как возведение в степень - ассоциативно справа.Таким образом, когда вы сталкиваетесь с рядом операторов с одинаковым приоритетом, вы хотите проанализировать их по порядку, если они оставлены ассоциативными, или в обратном порядке, если справа ассоциативно.Это просто определяет, толкаете ли вы или извлекаете данные из стека, поэтому не усложняет заданный алгоритм, а просто добавляет случаи, когда последовательные операторы имеют одинаковый приоритет (т. Е. Оценивают стек, если он левой ассоциативно, толкают в стек, если правой ассоциативно).

9 голосов
/ 04 января 2011

«Пятиминутное введение в ANTLR» включает пример арифметической грамматики.Это стоит проверить, тем более что antlr с открытым исходным кодом (лицензия BSD).

2 голосов
/ 04 января 2011

Несколько вариантов для вас:

  1. Повторно использовать существующий синтаксический анализатор выражений.Это будет работать, если вы будете гибки в синтаксисе и семантике.Хорошим вариантом, который я рекомендую, является встроенный в Java язык унифицированных выражений (изначально для использования в файлах JSP и JSF).

  2. Создайте собственный анализатор с нуля.Существует четко определенный способ написания синтаксического анализатора, который учитывает приоритет оператора и т. Д. Точное описание того, как это делается, выходит за рамки этого ответа.Если вы пойдете по этому пути, найдите хорошую книгу о дизайне компилятора.Теория синтаксического анализа языка будет рассмотрена в первых нескольких главах.Как правило, разбор выражений является одним из примеров.

  3. Используйте JavaCC или ANTLR для генерации лексера и парсера.Я предпочитаю JavaCC, но каждому свое.Просто гуглите "javacc samples" или "antlr samples".Вы найдете много.

Между 2 и 3, я настоятельно рекомендую 3, даже если вам придется изучать новые технологии.Существует причина, по которой были созданы генераторы синтаксических анализаторов.

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

Обновление: вот пример синтаксического анализатора языка выражений, который я написал с использованием JavaCC.Синтаксис свободно основан на языке унифицированных выражений.Это должно дать вам довольно хорошее представление о том, с чем вы столкнулись.

Содержимое org.eclipse.sapphire / plugins / org.eclipse.sapphire.modeling / src / org / eclipse / sapphire /моделирование / эл / анализатор / внутренний / ExpressionLanguageParser.jj

1 голос
/ 30 ноября 2014

данное выражение (5 + 2) * 7, которое мы можем принять как инфикс

Infix  :     (5+2)*7
Prefix :     *+527

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...