Давайте разберёмся, как можно вычислять арифметические выражения. Предположим, на вход нам поступает строка текста, которая содержит корректное арифметическое выражение.
Это выражение состоит из пробелов, чисел, скобок и знаков, обозначающих основные математические действия (плюс, минус, умножить, разделить). Нам нужно разобрать это выражение на отдельные элементы, а затем вычислить результат с учётом приоритетов математических операций. Например, умножение и деление должно выполняться раньше, чем сложение и вычитание. А часть выражения, заключённая в скобки, имеет приоритет над другими частями.
Обработку такого выражения можно разделить на три основных этапа:
- Разбиение строки на отдельные части
- Обработка этих частей с учётом математических операций
- Само вычисление
Разберём каждую из этих частей подробнее.
Лексический анализ
Символы, сгруппированные по некоторому признаку, называются токеном. Лексический анализ или токенизация – процесс разбиения входной строки на отдельные токены. В нашем случае каждое число – это отдельный токен. Также токенами являются открывающая скобка, закрывающая и знаки математических операций. Пробелы тоже являются токенами, но на вычисление они никак не влияют, поэтому мы их будем пропускать.
Нахождение значений выражений, используя программу вычитания
Наличие или отсутствие пробелов никак не должно влиять на корректность разбиения на токены. Если вместе с числом слитно написана скобка или знак плюс, мы должны на выходе всё равно получить два токена.
Такой парсинг можно было бы реализовать и вручную по следующему алгоритму. Проходимся по каждому символу строки и проверяем его:
- Если это пробел – ничего не делаем
- Если текущий символ имеет тот же тип, что и предыдущий, то прибавляем его к существующему токену.
- Если текущий символ имеет другой тип – начинаем «набирать» новый токен.
Однако писать такой код утомительно и можно легко ошибиться. Благо в Java есть стандартный класс для этого – StringTokenizer. Он разбивает исходную строку на подстроки по указанным разделителям и далее мы можем пройтись по ним в цикле.
Давайте создадим класс Lexer и сразу добавим в него константу с разделителями в виде строки:
public class Lexer <
private static final String DELIMITERS = » +-*/()» ;
>
Обратите внимание, что среди разделителей присутствует пробел.
Теперь давайте создадим перечисление TokenType, представляющее тип токена:
public enum TokenType <
BINARY_OPERATION ,
NUMBER ,
OPEN_BRACKET ,
CLOSE_BRACKET
>
Как видите, токен может быть числом, открывающей скобкой, закрывающей, а также бинарной операцией. Под бинарной операцией здесь понимается любая математическая операция с двумя аргументами.
Сам токен представим в виде интерфейса:
public interface Token <
TokenType type();
>
Его реализуют несколько record-классов, по одному на каждый тип. В каждом таком классе может быть свой набор дополнительных полей.
Функции языка выражений СКД
NumberToken – представляет собой число и содержит его значение:
BinaryOperationToken – токен бинарной операции:
Он содержит поле OperationType, которое также является перечислением:
public enum OperationType <
PLUS ,
MINUS ,
MULTIPLY ,
DIVIDE ,
>
Для скобок создадим ещё одну реализацию интерфейса Token:
public record OtherToken(TokenType type) implements Token <
>
Теперь вернёмся к лексеру и добавим в него метод getTokens():
Иначе если это бинарная операция, то извлекаем из стека два аргумента для неё: сначала правый, затем левый. Обратите внимание, что других типов токенов, кроме числа и операций здесь уже не будет.
else if (token instanceof BinaryOperationToken operation) <
var right = valueStack.pop();
var left = valueStack.pop();
Ну а далее выполняем действие, соответствующее типу операции и помещаем результат на вершину стека. При делении важно проверить правый аргумент (делитель), чтобы он не был равен нулю.
var result = switch (operation.operationType()) <
case PLUS -> left + right;
case MINUS -> left — right;
case MULTIPLY -> left * right;
case DIVIDE -> <
if (right == 0 ) <
throw new RuntimeException( «Divide by zero!» );
>
yield left / right;
>
>;
valueStack.push(result);
После выхода из цикла извлекаем единственный оставшийся элемент из стека – это и есть результат наших вычислений:
for (Token token : postfixExpression) <
// проверка типа токена и вычисления
>
return valueStack.pop();
Как видите, обратная польская нотация позволяет довольно легко вычислять арифметические выражения.
Соединяем всё воедино
Теперь вызовем последовательно лексер, конвертер и стековую машину, чтобы обработать исходное выражение целиком:
public int calculate(String expression) <
List tokens = lexer.getTokens(expression);
var postfixExpression = converter.convertToPostfix(tokens);
var result = stackMachine.evaluate(postfixExpression);
System.out.println(result);
return result;
>
Теперь протестируем нашу реализацию:
var calculator = new Calculator();
calculator.calculate( » 12*5 — 36 / 3″ ); // 48
calculator.calculate( «12 + 50 / 5 — 3 » ); // 19
calculator.calculate( «20 * ( 45 + 5 ) / 10» ); // 100
calculator.calculate(String.valueOf(Integer. MAX_VALUE )); // 2147483647
Рассмотренная в данной статье реализация калькулятора доступна на github.
Возможные улучшения
Наш калькулятор на текущий момент может обрабатывать только целые числа, представленные типом int. Если передать более «длинное» число, то при парсинге будет ошибка. Также хотелось бы обрабатывать десятичные дроби. Всё это довольно легко сделать, если заменить int на BigDecimal, а также доработать метод isNumber() в лексере, чтобы он допускал наличие десятичной точки в числе. При делении BigDecimal в стековой машине нужно учесть некоторые особенности округления, о которых я писал в статье Советы по работе с BigDecimal в Java.
Ну а дальше если к этому калькулятору добавить поддержку переменных, функций и управляющие конструкции (хотя бы if, else и while), то вы получите простейший интерпретатор языка программирования.
Источник: devmark.ru
Составить схему алгоритма и программу для вычисления выражения — Turbo Pascal (33622)
Помогите плиз составить 2 программы по этим задачам : 1)Составить схему алгоритма и программу для вычисления выражения Q=7tgx+(z 3 +sqrt(y)/abs(x-y)) Убедиться, что при заданных численных значениях исходных данных x = 0.3, y = 9, z = 2.2 результат вычисления Q = 3.7341 2). Ферма содержит в хозяйстве M коров и N телят. Суточное потребление сена коровой составляет A кг, теленка – в 2 раза меньше. Определить, сколько килограммов сена необходимо для коров и телят на 30 дней. Заранее спасибо
Var x,y,z,Q: real; Begin x:=0.3; Readln(x); y:=9; Readln(y); z:=2.2; Readln(z); Q:=(7*tg(x)+exp(3*ln(z)))+sqrt(y)/abs(x-y); Writeln(‘Q=’,Q:7:3); End.
Код к задаче: «Составить схему алгоритма и программу для вычисления выражения»
Листинг программы
var m,n,a:integer; s:real; begin writeln(‘Введите количество коров и телят’); readln(m,n); write(‘Введите суточное потребление сена коровой a=’); readln(a); s:=30*(a*m+a*n/2); write(‘S=’,s:0:1); readln end.
Источник: studassistent.ru
Построить блок-схему и написать программу вычисления выражения
Построить блок-схему и написать программу вычисления выражения. Подобрать контрольный пример Контрольный пример х=-10 y=-0.9129453 х=0 y=0 х=2 y=-2.18504 х=15 y=4.95 Блок-схема решения Программа REM Разветвляющийся процесс INPUT “Введите значение x=”; x IF x
На странице представлен фрагмент работы. Eго можно использовать, как базу для подготовки.
Часть выполненной работы
4,-18> Программа REM Одномерный массивы CLS INPUT “Введите количество элементов М=”; M DIM Z(M) PRINT “Исходный массив” FOR i=1 TO M INPUT Z(i) NEXT i FOR i=1 TO M PRINT Z(i) NEXT i SR=0 j=0 REM Нахождение среднего арифметического четных эл-тов FOR i=2 TO M STEP 2 SR=Z(i)+SR: j=J+1 NEXT i SR=SR/j PRINT “Среднее арифметическое четных элементов =”;SR REM Нахождение наименьшего элемента …
Купить уже готовую работу
Представление основных структур программирования: итерация, ветвление , повторение. Основные структуры составления программ. Операторы if, case, for, while и.т.д.
Источник: uchimatchast.ru