Search code examples
javajava.util.scannerdelimiter

Can Java "delimit input itself", without explicit delimiters?


My calculator, that I needed to make for my test task to apply for a Java course, is working great. But there's an issue I would like to resolve. If you type in, for example, "5+3" as opposed to "5 + 3", it doesn't work. Can my calculator be smart enough to delimit input without explicit delimiters (like spaces)?

In other words, how do I make my scanner split, for example, an input of 5+32 *2 into five tokens: 5, +, 32, *, and 2? If I don't have to overhaul my entire code, that would be even better!

import java.util.Scanner;
import java.io.IOException;
import java.text.DecimalFormat;

public class Introduction {
  private static double firstNum;
  private static double secondNum;
  private static double result;
  private static boolean dontKnow;
  private static String firstNumS;
  private static String secondNumS;
  private static String operationS;
  private static final DecimalFormat df = new DecimalFormat("#.##");

  public static void main(String[] args) {
    Scanner scanner = new Scanner(System.in);
    firstNumS = scanner.next();
    operationS = scanner.next();
    secondNumS = scanner.next();

    if(firstNumS.compareTo(operationS) > 15){
      switch(firstNumS){
      case "I":
      firstNum = 1;
      break;
      case "II":
      firstNum = 2;
      break;
      case "III":
      firstNum = 3;
      break;
      case "IV":
      firstNum = 4;
      break;
      case "V":
      firstNum = 5;
      break;
      case "VI":
      firstNum = 6;
      break;
      case "VII":
      firstNum = 7;
      break;
      case "VIII":
      firstNum = 8;
      break;
      case "IX":
      firstNum = 9;
      break;
      case "X":
      firstNum = 10;
      break;
      default:
      System.out.println("I don't know the first number!");
      dontKnow = true; }}
    else {
    firstNum = Integer.decode(firstNumS);
      if(firstNum > 10){
      System.out.println("It appears, the first number is too big for me!");
      dontKnow = true;
      }
      }

    if(secondNumS.compareTo(operationS) > 15) {
      switch(secondNumS){
      case "I":
      secondNum = 1;
      break;
      case "II":
      secondNum = 2;
      break;
      case "III":
      secondNum = 3;
      break;
      case "IV":
      secondNum = 4;
      break;
      case "V":
      secondNum = 5;
      break;
      case "VI":
      secondNum = 6;
      break;
      case "VII":
      secondNum = 7;
      break;
      case "VIII":
      secondNum = 8;
      break;
      case "IX":
      secondNum = 9;
      break;
      case "X":
      secondNum = 10;
      break;
      default:
      System.out.println("I don't know the second number!");
      dontKnow = true; }}
    else {
    secondNum = Integer.decode(secondNumS);
      if(secondNum > 10) {
      System.out.println("It appears, the second number is too big for me!");
      dontKnow = true; }}
  
    if(operationS.equals("+")) {
      result = firstNum + secondNum; }
    else if(operationS.equals("-")) {
      result = firstNum - secondNum; }
    else if(operationS.equals("*")){
      result = firstNum * secondNum; }
    else if(operationS.equals("/")){
      result = firstNum / secondNum; }
    else {
      System.out.println("I don't know such an operation!");
      dontKnow = true; }

    if(!(operationS.equals("/") && secondNum == 0)) {
      if(!dontKnow) {
        if(result / (int)result != 1) {
          if(String.valueOf(result).equals(df.format(result))) {
          System.out.println("It's " + result + "!"); }
          else {
          System.out.println("It's approximately " + df.format(result) + "!"); }}
        else {
        System.out.println("It's " + (int)result + "!"); }}}
    else {
      if(!dontKnow) {
      System.out.println("Gosh! I tried to divide it by zero, as you requested, but my virtual head nearly exploded! I need to recover..."); }
      else {
      System.out.println("Besides, you can't even divide by zero, I'm so told!"); }}
  }
}

Solution

  • Assuming you're using scanner, yes, it could. The scanner operates on the notion that a regexp serves as delimiter: Each match of the regex delimits, and whatever the regexp matches is tossed out (because nobody 'cares' about reading the spaces or the commas or whatever). The scanner then gives you stuff in between the delimiters.

    Thus, for you to end up with scanner stream '5', '+', and '3', you want a delimiter that delimits on the space between '5' / '+' and '+' / '3', whilst matching 0 characters otherwise those would be thrown out.

    You can do that, using regexp lookahead/lookbehind. You want a digit to the left and an operator to the right, or vice versa:

    String test = "53 + 2*35- 8";
    Scanner s = new Scanner(test);
    s.useDelimiter("\\s+|(?:(?<=\\d)(?=[-+/*]))|(?:(?=\\d)(?<=[-+/*]))");
    while (s.hasNext()) {
      System.out.println("NEXT: '" + s.next() + "'");
    }
    

    To break that convoluted regex open:

    • A|B|C means: A or B or C. That's the 'outermost' part of this regexp, we're looking for one of 3 distinct things to split on.
    • \\s+ means: 1 or more whitespace characters. Thus, input "5 20" would be split into 5 and 20. The whitespace is consumed (i.e. tossed out and not part of your tokens).
    • OR, positive lookbehind ((?<=X) means: Match if, looking backwards, you would see X), and X is \\d here - a digit. We then also have a positive lookahead: (?=X) means: Check for X being here, but don't consume it (or it would be thrown out, remember, the regex describes the delimiter, and the delimiter is thrown out). We look ahead for one of the symbols.
    • OR, that, but flipped about (first an operator, then a digit).

    NB: If you want to avoid the complexity of a regexp, you could just loop through each character, but you'd be building a little state machine, and have to take care of consecutive, non-space separated digits: You need to combine those (10 + 20 is not 1, 0, +, 2, 0 - it's 10 + 20).

    NB2: If you also want to support ( and ) you can edit the regex appropriately (They are, essentially, 'operators' and go in the list of operators), however, at some point you're essentially descriving a grammar for a formal language and should start looking into a parser generator. But that's all vastly more complicated than any of this.