简体   繁体   中英

Split a mathematical expression while handling negative numbers with Java

I'm working on an expression calculator in Java. I decided to first write a code for conversion to postfix and then write an reverse polish notation calculator. So far my calculator works great and can handle any expression including operators + - * / % .

The problem I'm having however is that it splits the expression using a space input.split(" ") so this means the expression must be entered ( 4 + ( 2 * ( -2 - 1 ) ) ) * 1.5 when I it should be able to be entered (4+(2*(-2-1)))*1.5 .

After hours of tinkering and I now know it cant work regex but would it be able to write a for loop that loops through two tokens of the string at a time and if they both equal an operator then you can assume that the second must be a negative value. Or if the Equation starts with an operator then it must be a negative value? Iterate through the string like this until the second operator gets to the end of the expression?

Here is some code I have been playing with trying to make a start at this but since I'm still quite new to programming I can't seem to get it to work.

String expression = "(4+(2*(-2--16)))*-1.5";
ArrayList<String> tokens = new ArrayList<String>();
String orig = null;
String regex = "[-+/*()]+";
String first = Character.toString(expression.charAt(0));
tokens.add(first);
for (int i = 0; i < expression.length(); i++) {
    char x = expression.charAt(i);
    String a = Character.toString(x);
    if (i >= 1){    //Check i is greater than or equal to 1
        char y = expression.charAt(i-1);
        String b = Character.toString(y);
        if(b.matches(regex) && x == '-'){
            orig = a;
        }else if(orig != null && orig.equals("-")){
            System.out.println(orig + a);
            tokens.add(orig + a);
            orig = null;
        }else{
            tokens.add(a);
        }
    }
}
for(String t:tokens){
    System.out.print(t+" ");
}

Thanks for any help, Ciaran.

Edit:

My question is how can I write a method to split a mathematical expression which while splitting can tell the difference '-' as a binary operator and '-' as a unary operator? Am I on the right lines with the idea of iterating through a string and comparing the two tokens? – Ciaran Ashton 6 mins ago

What I am trying to achieve I want to turn String expression = (4+(2*(-2-1))) into String[] expression = (, 4, (, 2, *, (, -2, -, 1, ), ), )

This is a job for a proper parser generator . The best known ones in the Java world are JavaCC and Antlr . I like to use JFlex paired with JavaCC.

What's nice about them is that you give tokens a different meaning based on the context. So, a minus can mean one thing in one place and something different in another place.

Using a parser is the better solution, but to answer your question as you asked it, you can use this regex, which will pretty much do what you want (not 100% but comes close):

(?<=[\(\)\+\-*\/\^A-Za-z])|(?=[\(\)\+\-*\/\^A-Za-z])

So, you will have to escape it and use it like this:

String input = ...;
String temp[] = input.split("(?<=[\\(\\)\\+\\-*\\/\\^A-Za-z])|(?=[\\(\\)\\+\\-*\\/\\^A-Za-z])");
System.out.println(Arrays.toString(temp));

Input:

7+4-(18/3)/2a^222+1ab

Output:

[7, +, 4, -, (, 18, /, 3, ), /, 2, a, ^, 222, +, 1, a, b]

See it in action here:
http://rubular.com/r/uHAObPwaln
http://ideone.com/GLFmo4

This can be the solution to your problem and problem like this although i have not tested this thoroughly on variety of data but approach is that-- whenever unary operator comes in expression(fully parenthesized expression) it will be preceded by '(' and followed by a number.

    String expression = "(4+(2*(-2-1)))*1.5";
    List<String> tokens = new ArrayList<String>();
    String prev = null;
    int c = 0;
    for (int i = 0; i < expression.length(); i++) {
        char x = expression.charAt(i);
        String a = Character.toString(x);
        if (i >= 1 && expression.charAt(i - 1) == '(' && x == '-') {
            prev = a;
        } else {
            if (prev != null && prev.equals("-")) {
                tokens.add(prev + a);
                prev = null;
            } else {
                tokens.add(a);
            }
            c++;
        }
    }

This is a version that only uses regular expressions. It matches your sample input but it won't handle situations where unary operators are placed in front of parentheses or if multiple unary operations are nested (eg "--1"):

// expression that matches standard Java number formats
// such as 1234, 12.5, and 1.3E-19
String number = "\\d+(?:\\.\\d+(?:(?:E|e)-?\\d+)?)?";
// expression that matches : 
// number
// number with unary operator (deemed unary if preceded by (,-,+,/, or *)
// (,),-,+,/, or *
String token = "(" + number + "|(?<=[(-+/*])-" + number + "|[-+/*()])?";
Pattern p = Pattern.compile(token);
Matcher m = p.matcher("(4+(2*(-2-1)))*1.5");
while (m.find()) {
  System.out.println(m.group(0));
}

Okay so after all the great advise from the guys I created a method that will take an input such as -1+2(4+(2*(-2--1)))*-1.5 and split it to an array, such as [-1, +, 2, (, 4, +, (, 2, *, (, -2, -, -1, ), ), ), *, -1.5] .

The way the method works is that it splits the input String using regex. With regex I was able to split all the numbers and operators. While this is great it wasn't able to handle negative values. Using regex it would always see - as a binary operator. I needed it to see it as a unary operator so that it could understand that it's a negative value. So what I did was compare each operator with the string that followed it. If this was also an operator I knew that the second one was a unary operator. I then also had to put in an if statement for if the first value was a - and if it was I knew that that was a unary operator.

Here's the code so far. I'm sure there is an easier way to do this using a parser, I just couldn't wrap my head around it.

import java.util.ArrayList;
import java.util.Arrays;


public class expSplit {

    public String[] splitExp(String theexp){
        ArrayList<String> tokens = new ArrayList<String>();
        //System.out.println(theexp);
        String expression = theexp.replaceAll("\\s+", "");
        //System.out.println(expression);
        String tem[] = expression.split("(?<=[-+*/%(),])(?=.)|(?<=.)(?=[-+*/%(),])");
        ArrayList<String> temp = new ArrayList<String>(Arrays.asList(tem));
        String orig = null;
        String regex = "[-+/%*]+";
        String first = temp.get(0);
        tokens.add(first);
        String secound = temp.get(1);
        if(first.equals("-")){
            tokens.remove(0);
            tokens.add(first+secound);
        }
        for (int i = 0; i < temp.size(); i++) {
            String a = temp.get(i);
            if (i >= 1){
                String b = temp.get(i-1);
                if(b.matches(regex) && a.matches("[-+]+")){
                    String c = temp.get(i-2);
                    if(c.matches("[-+]+")){
                        //System.out.println("MATCH");
                        break;
                    }else{
                        //System.out.println("NO MATCH");
                        orig = a;
                    }
                }else if(orig != null && orig.equals("-")){
                    tokens.add(orig + a);
                    orig = null;
                }else{
                    tokens.add(a);
                }
            }
        }
        if(first.equals("+")){
            tokens.remove(0);
        }
        if(first.equals("-")){
            tokens.remove(1);
        }
        String[]tokenArray = new String[tokens.size()];
        tokenArray = tokens.toArray(tokenArray);
        //System.out.print(tokens);
        return tokenArray;
    }
}

Thanks for the help, Ciaran

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM