diff --git a/homework-g595-ulyanin/pom.xml b/homework-g595-ulyanin/pom.xml index 66d384245..7517dbd0b 100644 --- a/homework-g595-ulyanin/pom.xml +++ b/homework-g595-ulyanin/pom.xml @@ -15,26 +15,66 @@ - ru.mipt.java2016 - homework-base - 1.0.0 + org.springframework.boot + spring-boot-dependencies + 1.4.2.RELEASE + pom + import + + org.springframework.boot + spring-boot-starter-web + RELEASE + ru.mipt.java2016 - homework-tests + homework-base 1.0.0 - test + com.google.guava guava 19.0 + org.springframework.security spring-security-core RELEASE + + + org.springframework.boot + spring-boot-devtools + true + RELEASE + + + + ru.mipt.java2016 + homework-tests + 1.0.0 + test + + + + 1.8 + + + + + spring-releases + https://repo.spring.io/libs-release + + + + + + spring-releases + https://repo.spring.io/libs-release + + \ No newline at end of file diff --git a/homework-g595-ulyanin/src/main/java/ru/mipt/java2016/homework/g595/ulyanin/task1/ShuntingYardCalculator.java b/homework-g595-ulyanin/src/main/java/ru/mipt/java2016/homework/g595/ulyanin/task1/ShuntingYardCalculator.java index fe13d7b7a..794576eca 100644 --- a/homework-g595-ulyanin/src/main/java/ru/mipt/java2016/homework/g595/ulyanin/task1/ShuntingYardCalculator.java +++ b/homework-g595-ulyanin/src/main/java/ru/mipt/java2016/homework/g595/ulyanin/task1/ShuntingYardCalculator.java @@ -4,8 +4,7 @@ import ru.mipt.java2016.homework.base.task1.Calculator; import ru.mipt.java2016.homework.base.task1.ParsingException; -import java.util.ArrayList; -import java.util.Stack; +import java.util.*; /** * Implementation of Calculator using Shunting Yard algorithm @@ -15,6 +14,72 @@ public class ShuntingYardCalculator implements Calculator { + private HashMap variablesValues = new HashMap<>(); + private HashMap functions = new HashMap<>(); + private HashSet defaultFunctions; + + private class Function { + private String name; + private ArrayList arguments; + private String expression; + private ArrayList postfix; + + + Function(String functionName, ArrayList arguments, String expression) throws ParsingException { + this.name = functionName; + this.arguments = arguments; + this.expression = expression; + this.postfix = infixToPostfix(splitExpressionToTokens(expression)); + } + + public Double apply(ArrayList argumentValues) throws ParsingException { + ArrayList newPostfix = replaceWithArguments(postfix, argumentValues); + return calculatePostfix(newPostfix); + } + + private ArrayList replaceWithArguments(ArrayList postfixToReplace, ArrayList argValues) { + ArrayList newPostfix = new ArrayList<>(); + for (Token token : postfixToReplace) { + int argN = arguments.indexOf(token.data); + if (argN == -1) { + newPostfix.add(token); + } else { + newPostfix.add(new Token(Double.toString(argValues.get(argN)), Token.TokenType.NUMBER)); + } + } + return newPostfix; + } + + public int getArity() { + return arguments.size(); + } + + } + + + + public ShuntingYardCalculator() { + try { + functions.put("sin", + new Function("sin", new ArrayList<>(Arrays.asList("x")), "sin(x)") + ); + functions.put("abs", + new Function("abs", new ArrayList<>(Arrays.asList("x")), "abs(x)") + ); + functions.put("max", + new Function("max", new ArrayList<>(Arrays.asList("x,y")), "max(x,y)") + ); + } catch (ParsingException e) { + e.printStackTrace(); + } + this.defaultFunctions = new HashSet<>(functions.keySet()); + } + + + public boolean isLocalVariable(Token token) { + return variablesValues.containsKey(token.data); + } + public double calculate(String expression) throws ParsingException { if (expression == null) { throw new ParsingException("Null expression"); @@ -29,14 +94,33 @@ private static ArrayList splitExpressionToTokens(String expression) throw return tokenizer.getTokens(expression); } - private static ArrayList infixToPostfix(ArrayList tokens) throws ParsingException { + private ArrayList infixToPostfix(ArrayList tokens) throws ParsingException { ArrayList postfix = new ArrayList<>(); Stack operatorStack = new Stack<>(); boolean mayBeUnaryOperator = true; Token lastToken = null; for (Token token : tokens) { - if (token.isOperatorToken()) { + // variables: +// if (lastToken != null && !token.isOpenBraceToken() && lastToken.isFunctionToken()) { +// postfix.add(operatorStack.pop()); +// } + + if (token.isNumberToken()) { + postfix.add(token); + mayBeUnaryOperator = false; + } else if (token.isFunctionToken()) { + operatorStack.add(new TokenOperator(token.getData(), Token.TokenType.FUNCTION)); + mayBeUnaryOperator = false; + } else if (token.isArgumentSeparatorToken()) { + while (!operatorStack.isEmpty() && !operatorStack.peek().isOpenBraceToken()) { + postfix.add(operatorStack.pop()); + } + if (operatorStack.isEmpty()) { + throw new ParsingException("missing ',' or '(' in function declaration"); + } + mayBeUnaryOperator = true; // because max(0, -4) is possible + } else if (token.isOperatorToken()) { // operator case TokenOperator currentOperator = new TokenOperator(token.getData(), mayBeUnaryOperator); while (!operatorStack.isEmpty()) { @@ -63,13 +147,15 @@ private static ArrayList infixToPostfix(ArrayList tokens) throws P postfix.add(operatorStack.pop()); } if (operatorStack.isEmpty()) { - throw new ParsingException("there are no '(' before ')'"); + throw new ParsingException("there is no '(' before ')'"); + } + if (operatorStack.peek().isFunctionToken()) { + postfix.add(operatorStack.pop()); } operatorStack.pop(); mayBeUnaryOperator = false; } else { - postfix.add(token); - mayBeUnaryOperator = false; + throw new ParsingException("unexpected token type"); } lastToken = token; } @@ -83,23 +169,55 @@ private static ArrayList infixToPostfix(ArrayList tokens) throws P return postfix; } - private static double calculatePostfix(ArrayList postfix) throws ParsingException { + private double calculatePostfix(ArrayList postfix) throws ParsingException { Stack operands = new Stack<>(); for (Token token : postfix) { if (token instanceof TokenOperator) { - if (!((TokenOperator) token).isUnary()) { + if (token.isFunctionToken()) { + Double var; + if (variablesValues.containsKey(token.data)) { + var = variablesValues.get(token.data); + } else if (defaultFunctions.contains(token.data)) { + double var1 = operands.pop(); + if (token.data.equals("sin")) { + var = Math.sin(var1); + } else if (token.data.equals("abs")) { + var = Math.abs(var1); + } else if (token.data.equals("max")) { + double var2 = operands.pop(); + var = Math.max(var1, var2); + } else { + throw new ParsingException("unmapped function " + token.data); + } + } else { + Function f = functions.get(token.data); + if (f == null) { + throw new ParsingException("unknown function name " + token.data); + } + int arity = f.getArity(); + ArrayList arguments = new ArrayList<>(); + for (int i = 0; i < arity; ++i) { + arguments.add(operands.pop()); + } + Collections.reverse(arguments); + var = f.apply(arguments); + } + operands.push(var); + } else if (!((TokenOperator) token).isUnary()) { if (operands.size() < 2) { throw new ParsingException("there are no two operands to binary operator " + token.getData()); } Double var2 = operands.pop(); Double var1 = operands.pop(); operands.push(((TokenOperator) token).apply(var1, var2)); - } else { + } else if (token.isOperatorToken()) { if (operands.size() < 1) { throw new ParsingException("there are no operands to unary operator" + token.getData()); } Double var = operands.pop(); operands.push(((TokenOperator) token).apply(var)); + } else { + throw new ParsingException("unexpected tokenOperatorType"); } } else { operands.push(token.getValue()); @@ -111,4 +229,68 @@ private static double calculatePostfix(ArrayList postfix) throws ParsingE } return result; } + + public String getVariableValue(String variableName) throws ParsingException { + if (!variablesValues.containsKey(variableName)) { + throw new ParsingException("invalid variable name"); + } + return Double.toString(variablesValues.get(variableName)); + } + + public String addVariable(String variableName, String valueExpression) throws ParsingException { + variablesValues.put(variableName, calculate(valueExpression)); + return getVariableValue(variableName); + } + + public void deleteVariable(String variableName) throws ParsingException { + if (!variablesValues.containsKey(variableName)) { + throw new ParsingException("variable " + variableName + " does not exist"); + } + variablesValues.remove(variableName); + } + + public void addFunction(String functionName, ArrayList params, String expression) throws ParsingException { + if (defaultFunctions.contains(functionName)) { + throw new ParsingException("trying to redefine default function " + functionName); + } + Function f = new Function(functionName, params, expression); + functions.put(functionName, f); + } + + public void deleteFunction(String functionName) throws ParsingException { + if (!functions.containsKey(functionName)) { + throw new ParsingException("function " + functionName + " does not exist"); + } + if (defaultFunctions.contains(functionName)) { + throw new ParsingException("trying to delete default function"); + } + functions.remove(functionName); + } + + public ArrayList getFunctionList() { + return new ArrayList<>(functions.keySet()); + } + + public String getFunctionDescription(String functionName) throws ParsingException { + if (!functions.containsKey(functionName)) { + throw new ParsingException("function " + functionName + " does not exist"); + } + Function f = functions.get(functionName); + StringBuilder function = new StringBuilder(); + function.append(functionName); + function.append('('); + for (int i = 0; i < f.getArity(); ++i) { + if (i != 0) { + function.append(','); + } + function.append(f.arguments.get(i)); + } + function.append(')'); + function.append(" -> " + f.expression); + return function.toString(); + } + + public ArrayList getVariableList() { + return new ArrayList<>(variablesValues.keySet()); + } } diff --git a/homework-g595-ulyanin/src/main/java/ru/mipt/java2016/homework/g595/ulyanin/task1/Token.java b/homework-g595-ulyanin/src/main/java/ru/mipt/java2016/homework/g595/ulyanin/task1/Token.java index a5253302a..44e43003c 100644 --- a/homework-g595-ulyanin/src/main/java/ru/mipt/java2016/homework/g595/ulyanin/task1/Token.java +++ b/homework-g595-ulyanin/src/main/java/ru/mipt/java2016/homework/g595/ulyanin/task1/Token.java @@ -9,7 +9,7 @@ public class Token { - public enum TokenType { OPERATOR, NUMBER, BRACE_OPEN, BRACE_CLOSE } + public enum TokenType { OPERATOR, NUMBER, BRACE_OPEN, BRACE_CLOSE, FUNCTION, ARGS_SEPARATOR } protected String data; protected TokenType type; @@ -39,6 +39,14 @@ public boolean isCloseBraceToken() { return type == TokenType.BRACE_CLOSE; } + public boolean isArgumentSeparatorToken() { + return type == TokenType.ARGS_SEPARATOR; + } + + public boolean isFunctionToken() { + return type == TokenType.FUNCTION; + } + public double getValue() throws ParsingException { if (type != TokenType.NUMBER) { throw new ParsingException("can not cast not a number to double"); diff --git a/homework-g595-ulyanin/src/main/java/ru/mipt/java2016/homework/g595/ulyanin/task1/TokenOperator.java b/homework-g595-ulyanin/src/main/java/ru/mipt/java2016/homework/g595/ulyanin/task1/TokenOperator.java index b44c21009..8a68fd2a2 100644 --- a/homework-g595-ulyanin/src/main/java/ru/mipt/java2016/homework/g595/ulyanin/task1/TokenOperator.java +++ b/homework-g595-ulyanin/src/main/java/ru/mipt/java2016/homework/g595/ulyanin/task1/TokenOperator.java @@ -29,7 +29,8 @@ public int getPrecedence() throws ParsingException { final String ops = "()+-*/"; int index = ops.indexOf(data); if (index == -1) { - throw new ParsingException("unknown operator " + data); + return 4; +// throw new ParsingException("unknown operator " + data); } if (isSubtruct() && isUnary()) { return 3; diff --git a/homework-g595-ulyanin/src/main/java/ru/mipt/java2016/homework/g595/ulyanin/task1/Tokenizer.java b/homework-g595-ulyanin/src/main/java/ru/mipt/java2016/homework/g595/ulyanin/task1/Tokenizer.java index 2917919bb..afa85fb4f 100644 --- a/homework-g595-ulyanin/src/main/java/ru/mipt/java2016/homework/g595/ulyanin/task1/Tokenizer.java +++ b/homework-g595-ulyanin/src/main/java/ru/mipt/java2016/homework/g595/ulyanin/task1/Tokenizer.java @@ -5,7 +5,8 @@ import java.util.ArrayList; /** - * Created by ulyanin on 11.10.16. + * @author ulyanin + * @since 11.10.16. */ public class Tokenizer { private int currentPosition; @@ -36,6 +37,10 @@ public ArrayList getTokens(String expr) throws ParsingException { currentToken = new Token(String.valueOf(c), Token.TokenType.BRACE_CLOSE); } else if (isOperator(c)) { currentToken = new Token(String.valueOf(c), Token.TokenType.OPERATOR); + } else if (isFunctionArgumentSeparator(c)) { + currentToken = new Token(String.valueOf(c), Token.TokenType.ARGS_SEPARATOR); + } else if (isFunctionName(c)) { + currentToken = new Token(readFunctionName(), Token.TokenType.FUNCTION); } else { throw new ParsingException("unknown symbol '" + c + "'"); } @@ -44,6 +49,8 @@ public ArrayList getTokens(String expr) throws ParsingException { return result; } + + private char getCurrentChar() { return expression.charAt(currentPosition); } @@ -72,11 +79,19 @@ public static boolean isCloseBrace(char c) { return c == ')'; } + public static boolean isFunctionArgumentSeparator(char c) { + return c == ','; + } + public static boolean isOperator(char c) { return c == '+' || c == '*' || c == '-' || c == '/'; } - private String readNumberToken() { + private boolean isFunctionName(char c) { + return Character.isLetter(c) || Character.isDigit(c) || c == '_'; + } + + private String readNumberToken() throws ParsingException { StringBuilder token = new StringBuilder(); while (characterExist() && isNumberCharacter(getCurrentChar())) { token.append(getCurrentChar()); @@ -87,4 +102,16 @@ private String readNumberToken() { } return token.toString(); } + + private String readFunctionName() throws ParsingException { + StringBuilder token = new StringBuilder(); + while (characterExist() && isFunctionName(getCurrentChar())) { + token.append(getCurrentChar()); + readNextCharacter(); + } + if (characterExist()) { + unreadCharacter(); + } + return token.toString(); + } } diff --git a/homework-g595-ulyanin/src/main/java/ru/mipt/java2016/homework/g595/ulyanin/task4/RestCalculatorController.java b/homework-g595-ulyanin/src/main/java/ru/mipt/java2016/homework/g595/ulyanin/task4/RestCalculatorController.java new file mode 100644 index 000000000..559ec1198 --- /dev/null +++ b/homework-g595-ulyanin/src/main/java/ru/mipt/java2016/homework/g595/ulyanin/task4/RestCalculatorController.java @@ -0,0 +1,84 @@ +package ru.mipt.java2016.homework.g595.ulyanin.task4; + +import org.springframework.web.bind.annotation.*; +import ru.mipt.java2016.homework.base.task1.ParsingException; +import ru.mipt.java2016.homework.g595.ulyanin.task1.ShuntingYardCalculator; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by ulyanin on 20.12.16. + */ +@RestController +public class RestCalculatorController { + private ShuntingYardCalculator calculator = new ShuntingYardCalculator(); + + @RequestMapping(path = "/variable/{variableName}", method = RequestMethod.GET) + @ResponseBody + public String getVariableValue(@PathVariable String variableName) { + try { + return String.valueOf(calculator.getVariableValue(variableName)); + } catch (ParsingException err) { + return "Incorrect expression"; + } + } + + @RequestMapping(path = "/variable/{variableName}", method = RequestMethod.PUT) + public String putValueOfVariable(@PathVariable String variableName, + @RequestBody String valueExpression) throws ParsingException { + System.out.println(variableName); + System.out.println(valueExpression); + return calculator.addVariable(variableName, valueExpression); + } + + @RequestMapping(path = "/variable/{variableName}", method = RequestMethod.DELETE) + public void deleteVariable(@PathVariable String variableName) throws ParsingException { + calculator.deleteVariable(variableName); + } + + @RequestMapping(path = "/variable", method = RequestMethod.GET) + @ResponseBody + public List getVariableList() { + return calculator.getVariableList(); + } + + @RequestMapping(path = "/function/{functionName}", method = RequestMethod.GET) + @ResponseBody + public String getFunctionDescription(@PathVariable String functionName) throws ParsingException { + return calculator.getFunctionDescription(functionName); + } + + @RequestMapping(path = "/function/{functionName}", method = RequestMethod.PUT) + public String putFunction(@PathVariable String functionName, + @RequestParam(value = "args") List params, + @RequestBody String expression) throws ParsingException { + calculator.addFunction(functionName, new ArrayList<>(params), expression); + return "add function ok"; + } + + @RequestMapping(path = "/function/{functionName}", method = RequestMethod.DELETE) + public String deleteFunction(@PathVariable String functionName) throws ParsingException { + calculator.deleteFunction(functionName); + return "delete ok"; + } + + @RequestMapping(path = "/function", method = RequestMethod.GET) + @ResponseBody + public List getFunctionList() { + return calculator.getFunctionList(); + } + + @RequestMapping(path = "/eval", method = RequestMethod.POST) + @ResponseBody + public String calculate(@RequestBody String expression) { + System.out.println(expression); + try { + return String.valueOf(calculator.calculate(expression)); + } catch (ParsingException e) { + e.printStackTrace(); + return e.getMessage(); + } + } +} + diff --git a/homework-g595-ulyanin/src/main/java/ru/mipt/java2016/homework/g595/ulyanin/task4/Service.java b/homework-g595-ulyanin/src/main/java/ru/mipt/java2016/homework/g595/ulyanin/task4/Service.java new file mode 100644 index 000000000..53774362b --- /dev/null +++ b/homework-g595-ulyanin/src/main/java/ru/mipt/java2016/homework/g595/ulyanin/task4/Service.java @@ -0,0 +1,19 @@ +package ru.mipt.java2016.homework.g595.ulyanin.task4; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.web.support.SpringBootServletInitializer; +import org.springframework.context.annotation.Configuration; + +/** + * Created by ulyanin on 19.12.16. + */ + +@Configuration +@SpringBootApplication +public class Service extends SpringBootServletInitializer { + public static void main(String[] args) { + SpringApplication.run(Service.class, args); + } +} +