diff --git a/homework-g596-narsia/pom.xml b/homework-g596-narsia/pom.xml new file mode 100644 index 000000000..6f94e79f6 --- /dev/null +++ b/homework-g596-narsia/pom.xml @@ -0,0 +1,28 @@ + + + + mipt-java-2016 + ru.mipt.java2016 + 1.0.0 + + 4.0.0 + + homework-g596-narsia + + + ru.mipt.java2016 + homework-base + 1.0.0 + + + ru.mipt.java2016 + homework-tests + 1.0.0 + test + + + + + \ No newline at end of file diff --git a/homework-g596-narsia/src/main/java/ru/mipt/java2016/homework/g596/narsia/task1/MyCalculator.java b/homework-g596-narsia/src/main/java/ru/mipt/java2016/homework/g596/narsia/task1/MyCalculator.java new file mode 100644 index 000000000..64797500f --- /dev/null +++ b/homework-g596-narsia/src/main/java/ru/mipt/java2016/homework/g596/narsia/task1/MyCalculator.java @@ -0,0 +1,389 @@ +package ru.mipt.java2016.homework.g596.narsia.task1; + +import ru.mipt.java2016.homework.base.task1.Calculator; +import ru.mipt.java2016.homework.base.task1.ParsingException; + +import java.util.Stack; + + +public class MyCalculator implements Calculator { + private enum SymbolType { DIGIT, POINT, USUAL_OPERATOR, MINUS, SPACE, + OPENING_BRACKET, CLOSING_BRACKET, FIRST } + + //Получает на вход символ, передает его + //характеристику (цифра, точка, пробел...) + //Можно было бы и char передать, просто + //использую метод equals + private SymbolType symbolType(Character symbol) throws ParsingException { + if (Character.isDigit(symbol)) { + return SymbolType.DIGIT; + } + if (Character.isWhitespace(symbol)) { + return SymbolType.SPACE; + } + switch (symbol) { + case '.': + return SymbolType.POINT; + case '+': + case '*': + case '/': + return SymbolType.USUAL_OPERATOR; + case '-': + return SymbolType.MINUS; + case '(': + return SymbolType.OPENING_BRACKET; + case ')': + return SymbolType.CLOSING_BRACKET; + case '~': + return SymbolType.FIRST; + default: + throw new ParsingException("Invalid symbol"); + } + } + + //Проверка корректности выражения , за + //исключением неправильного баланса скобок + private void isAlmostValid(String expression) throws ParsingException { + if (expression == null) { + throw new ParsingException("Invalid expression"); + } + char prevSymbol = '~'; + char curSymbol; + char importantPrevSymbol = '~'; + boolean pointFlag = false; + boolean spaceFlag = false; + + for (int index = 0; index < expression.length(); ++index) { + curSymbol = expression.charAt(index); + switch (symbolType(curSymbol)) { + case DIGIT: + if (symbolType(prevSymbol) == SymbolType.SPACE) { + if ((symbolType(importantPrevSymbol) == SymbolType.DIGIT) || + (symbolType(importantPrevSymbol) == SymbolType.CLOSING_BRACKET)) { + throw new ParsingException("Invalid expression"); + } + } + if (symbolType(prevSymbol) == SymbolType.CLOSING_BRACKET) { + throw new ParsingException("Invalid expression"); + } + break; + + case POINT: + if (symbolType(prevSymbol) == SymbolType.DIGIT) { + if (!pointFlag) { + pointFlag = true; + } else { + throw new ParsingException("2 points in one number"); + } + } else { + throw new ParsingException("Invalid expression"); + } + break; + + case USUAL_OPERATOR: + if ((symbolType(importantPrevSymbol) == SymbolType.USUAL_OPERATOR) || + (importantPrevSymbol == '(') || (importantPrevSymbol == '~') || + (importantPrevSymbol == '.')) { + throw new ParsingException("Invalid expression"); + } + if (symbolType(importantPrevSymbol) == SymbolType.DIGIT) { + pointFlag = false; + } + break; + + case MINUS: + if (importantPrevSymbol == '.') { + throw new ParsingException("Invalid expression"); + } + if (symbolType(importantPrevSymbol) == SymbolType.DIGIT) { + pointFlag = false; + } + break; + + case SPACE: + if (prevSymbol == '.') { + throw new ParsingException("Invalid expression"); + } + if (symbolType(prevSymbol) == SymbolType.DIGIT) { + pointFlag = false; + } + break; + + case OPENING_BRACKET: + if ((symbolType(importantPrevSymbol) == SymbolType.DIGIT) || + (importantPrevSymbol == '.') || (importantPrevSymbol == ')')) { + throw new ParsingException("Invalid expression"); + } + break; + + case CLOSING_BRACKET: + if (symbolType(importantPrevSymbol) == SymbolType.DIGIT) { + pointFlag = false; + } + if ((importantPrevSymbol == '.') || (importantPrevSymbol == '(') || + (importantPrevSymbol == '~') || + (symbolType(importantPrevSymbol) == SymbolType.USUAL_OPERATOR) || + (importantPrevSymbol == '-')) { + throw new ParsingException("Invalid expression"); + } + break; + + default: + throw new ParsingException("Invalid expression"); + } + prevSymbol = curSymbol; + if (symbolType(curSymbol) != SymbolType.SPACE) { + importantPrevSymbol = curSymbol; + spaceFlag = true; + } + } + if (!spaceFlag) { + throw new ParsingException("Invalid expression"); + } + } + + //Удаляет из корректного выражения все + //пробельные символы (важно суачала запустить + //метод isAlmostValid, т. к. при удалении + //пробелов можно потерять недопустимый случай + //« цифра - пробел - цифра ») + private String removeSpaces(String expression) { + StringBuilder result = new StringBuilder(expression.length()); + for (int index = 0; index < expression.length(); ++index) { + if (!Character.isWhitespace(expression.charAt(index))) { + result.append(expression.charAt(index)); + } + } + return result.toString(); + } + + //Заменяет в корректной строке без пробельных + //символов все унарные минусы на бинарные + private String removeUnaryMinuses(String expressionWithoutSpaces) throws ParsingException { + expressionWithoutSpaces = expressionWithoutSpaces.concat("~"); + StringBuilder result = new StringBuilder(expressionWithoutSpaces.length()); + boolean bracketFlag = false; + for (int index = 0; index < expressionWithoutSpaces.length(); ++index) { + if (expressionWithoutSpaces.charAt(index) == '-') { + if (index == 0) { + result.append('0'); + } else { + try { + switch (symbolType(expressionWithoutSpaces.charAt(index - 1))) { + case OPENING_BRACKET: + result.append("0-"); + continue; + case USUAL_OPERATOR: + case MINUS: + result.append("(0-"); + bracketFlag = true; + continue; + default: + break; + } + } catch (Exception ParsingException) { + throw new ParsingException("Invalid symbol"); + } + } + } + if ((!Character.isDigit(expressionWithoutSpaces.charAt(index))) && + (expressionWithoutSpaces.charAt(index) != '.') && (index > 0) && (bracketFlag)) { + result.append(")"); + bracketFlag = false; + } + if (expressionWithoutSpaces.charAt(index) != '~') { + result.append(expressionWithoutSpaces.charAt(index)); + } + } + return result.toString(); + } + + private enum Situations { PUSH, PUSH_LAST, REMOVE, + RESULT, EXCEPTION } + + //Таблица зависимости действий, производимых + //со строкой, от двух символов. Первый из них — + //текущий считанный, второй — тот, что лежит в + //вершине стека + private Situations getNumOfSituation(char first, char second) throws ParsingException { + switch (first) { + case '~': + try { + if ((symbolType(second) == SymbolType.USUAL_OPERATOR) || + (second == '-') || (second == '(')) { + return Situations.PUSH; + } + } catch (Exception ParsingException) { + throw new ParsingException("Invalid symbol"); + } + + if (second == '~') { + return Situations.RESULT; + } + if (second == ')') { + return Situations.EXCEPTION; + } + case '+': + case '-': + if ((second == '*') || (second == '/') || (second == '(')) { + return Situations.PUSH; + } + if ((second == '~') || (second == ')') || + (second == '+') || (second == '-')) { + return Situations.PUSH_LAST; + } + case '*': + case '/': + if (second == '(') { + return Situations.PUSH; + } + if ((second == '~') || (second == ')') || + (symbolType(second) == SymbolType.USUAL_OPERATOR) || + (second == '-')) { + return Situations.PUSH_LAST; + } + case '(': + if ((second == '(') || (symbolType(second) == SymbolType.USUAL_OPERATOR) || + second == '-') { + return Situations.PUSH; + } + if (second == ')') { + return Situations.REMOVE; + } + if (second == '~') { + return Situations.EXCEPTION; + } + default: + return Situations.EXCEPTION; + } + } + + //Принимает корректную строку без пробелов и + //унарных операторов, возвращает ее обратную + //польскую запись (с пробелами в качестве + //разделителей) + private String getRPN(String expression) throws ParsingException { + expression = expression.concat("~"); + Character curSymbol; + Character prevSymbol = '~'; + Stack operators = new Stack<>(); + StringBuilder rpn = new StringBuilder(expression.length()); + operators.push('~'); + + int index = 0; + while (true) { + curSymbol = expression.charAt(index); + if (index > 0) { + prevSymbol = expression.charAt(index - 1); + } + switch (symbolType(curSymbol)) { + case DIGIT: + case POINT: + if ((Character.isDigit(prevSymbol)) || (prevSymbol == '.') || + (prevSymbol == '~')) { + rpn.append(curSymbol); + ++index; + } else { + rpn.append(" "); + rpn.append(curSymbol); + ++index; + } + continue; + + default: + break; + } + + switch (getNumOfSituation(operators.peek(), curSymbol)) { + case PUSH: + operators.push(curSymbol); + ++index; + break; + case PUSH_LAST: + rpn.append(" "); + rpn.append(operators.peek()); + operators.pop(); + break; + case REMOVE: + operators.pop(); + ++index; + break; + case RESULT: + return rpn.toString(); + case EXCEPTION: + throw new ParsingException("Invalid bracket balance"); + default: + break; + } + } + } + + //Возвращает результат применения оператора к + //операндам + private double doOperation(double first, double second, char operator) { + switch (operator) { + case '+': + return first + second; + case '-': + //сейчас будет костыль + if ((first == 0) && (second == 0)) { + return -0.0; + } + return first - second; + case '*': + return first * second; + case '/': + return first / second; + default: + return -1.0; + } + } + + //По обратной польской записи выражения + //вычисляет его значение. При считывании оператора + //из стека удаляем 2 верхних числа и кладем в стек + //результат операции, примененной к этим двум + //числам. При считывании числа просто кладем его в + //стек. Для корректных выражений к концу работы в + //стеке останется всего одно число — его и + //возвращаем + @Override + public double calculate(String expression) throws ParsingException { + isAlmostValid(expression); + String withoutSpaces = removeSpaces(expression); + String withoutSpacesAndUnaryMinuses = removeUnaryMinuses(withoutSpaces); + String rpn = getRPN(withoutSpacesAndUnaryMinuses); + Stack numbers = new Stack<>(); + Double first; + Double second; + StringBuilder curNumber = new StringBuilder(); + char curChar = '~'; + char prevChar; + for (int cnt = 0; cnt < rpn.length(); ++cnt) { + prevChar = curChar; + curChar = rpn.charAt(cnt); + switch (symbolType(curChar)) { + case DIGIT: + case POINT: + curNumber.append(curChar); + break; + case USUAL_OPERATOR: + case MINUS: + second = numbers.peek(); + numbers.pop(); + first = numbers.peek(); + numbers.pop(); + numbers.push(doOperation(first, second, curChar)); + break; + case SPACE: + if (Character.isDigit(prevChar)) { + numbers.push(Double.parseDouble(curNumber.toString())); + curNumber.delete(0, curNumber.length()); + } + default: + break; + } + } + return numbers.peek(); + } +} diff --git a/homework-g596-narsia/src/test/java/ru/mipt/java2016/homework/g596/narsia/task1/MyCalculatorTest.java b/homework-g596-narsia/src/test/java/ru/mipt/java2016/homework/g596/narsia/task1/MyCalculatorTest.java new file mode 100644 index 000000000..2f77629d7 --- /dev/null +++ b/homework-g596-narsia/src/test/java/ru/mipt/java2016/homework/g596/narsia/task1/MyCalculatorTest.java @@ -0,0 +1,13 @@ +package ru.mipt.java2016.homework.g596.narsia.task1; + +import ru.mipt.java2016.homework.base.task1.Calculator; +import ru.mipt.java2016.homework.tests.task1.AbstractCalculatorTest; + + + +public class MyCalculatorTest extends AbstractCalculatorTest { + @Override + protected Calculator calc() { + return new MyCalculator(); + } +} diff --git a/pom.xml b/pom.xml index b507bc7ef..c33d22fd0 100644 --- a/pom.xml +++ b/pom.xml @@ -15,6 +15,7 @@ homework-base homework-tests homework-g596-ivanova + homework-g596-narsia homework-g597-povarnitsyn homework-g596-kupriyanov homework-g595-kryloff