All pastes #86662 Raw Edit

Someone

public text v1 · immutable
·published 2006-07-13 03:09 UTC
rendered paste body
/*
 * Expressions.java
 *
 * Version:
 *     $Id$
 *
 * Revisions:
 *     $Log$
 */ 

import java.util.Random;
import java.util.Scanner;

/**
 * An application that allows people to practice calculating
 * simple Java expressions with precedence.
 *
 */
public class Expressions {

  // set up the numbers used in expressions
  // number of segments should always be odd
  private final static int MAX_SEGMENTS = 9;
  private final static int MINIMUM_NUMBER_RANGE = 0;
  private final static int MAXIMUM_NUMBER_RANGE = 9;

  //Display options
  private final static int CONTINUE=0, EXPRESSION=1, RESULT=2, SCORE=3, END=4;


  //Set up score and timer
  private int score = 0;
  private long startTime=0, totalTime=0;


  // variables for the data input
  private Scanner input = new Scanner( System.in );


  // random will be able to choose things randomly for us for the expressions
  private Random random = new Random();

  // Variables used in the program
  private String expression;
  private String result = "0";

  /**
   * The main program.
   *
   * @param    args    command line arguments (ignored)
   */
  public static void main( String[] args ) {
    Expressions expressions = new Expressions();
    expressions.runGame(); 
  }

  /**
   * In order to start a sequence of expressions this method must
   * be called.
   *
   */
  public void runGame( ) {
     boolean done = false;
     startTime = System.currentTimeMillis();
 
    while( !done ) {
      if (!done ) {
	  expression = makeExpression();
	  display( EXPRESSION );
	  result = solveExpression( expression );
	  String userResult = getUserInput();

        if (userResult.matches(result)) {
          score += 10;
        }

	  // always display the correct result in the end
	  display( RESULT );

      }

      display( CONTINUE );

      // we see if we want to go again
      done = wantToQuit();
    }

    // print out completed information
    display( END );
   }

  //Prints output based on the type of text that needs to be displayed
  private void display( int typeToDisplay ) {

    totalTime = (System.currentTimeMillis() - startTime) / 1000;

    switch(typeToDisplay){
      case 0:
        System.out.println();
        System.out.println("--------------------------------------------------------------------");
        System.out.print("Type a 'q' to quit or any other key to continue: ");
        break;
      case 1:
        System.out.println();
        System.out.println("--------------------------------------------------------------------");
        System.out.println( "Calculate the value of the following expression: " + 
		           expression);
        break;
      case 2:
        System.out.println();
        System.out.println("The answer is: " + result );
        System.out.println("Your score is: " + score + " points in " + totalTime + " seconds.");
        break;
      case 3:
        break;
      case 4:
        System.out.println();
        System.out.println("--------------------------------------------------------------------");
        System.out.println("FINAL SCORE: " + score + " points in " + totalTime + " seconds.");
        System.out.println("--------------------------------------------------------------------");
        break;
    }
  }

  /**
   * A simple routine to get input from the user.
   *
   * @return The answer to an expression. Should be numeric.
   */
  private String getUserInput( ) {
    System.out.print("Enter your answer: ");
    
    return(input.nextLine());
  }

  /** 
   * @return True if the user hits the 'q' key to quit and false otherwise.
   */
  private boolean wantToQuit() {    
    return( input.nextLine().equalsIgnoreCase("q") );
  }

  /**
   * A method that makes expressions of a certain length with certain predefined
   * operators from a range of integer numbers.
   *
   * @return An expression in string form without spaces separating elements.
   */
  public String makeExpression( ) {
    // We use a stringbuffer as string will keep making new objects if we change it whereas
    // stringbuffer allows us to append new characters onto the end of it.
    StringBuffer segments = new StringBuffer("");

    // we keep a count of where we are so we know when to put in operators vs. numbers.
    int currentSegment = 0;

    int segmentsForThisExpression = random.nextInt( MAX_SEGMENTS-3 ) +3;
    if (segmentsForThisExpression % 2 == 0 ) {
      segmentsForThisExpression++;
    }

    // We're not going to handle creating divIndex by zero situations in here.
    // They will be handled by those that solve the equation.

    while ( currentSegment < segmentsForThisExpression ) {
      if ( currentSegment % 2 == 1 ) {
	// for statements that must take two arguments, odd numbers are always operators
	// we're only going to put the '-' sign in as a unary operator when
	// we're generating numbers
	int operator = random.nextInt( 5 );
	String nextOperator = "*";
	switch( operator ) {
	  case 1: nextOperator = "/";
	    break;
	  case 2: nextOperator = "%";
	    break;
	  case 3: nextOperator = "+";
	    break;
	  case 4: nextOperator = "-";
	    break;
	}
	segments.append( nextOperator );
      } else {
	// The "" is there to turn the whole expression into a string automagically for us rather
	// than a number type.
	String nextNumber = "" + 
	  ( random.nextInt( MAXIMUM_NUMBER_RANGE + Math.abs(MINIMUM_NUMBER_RANGE) ) - Math.abs(MINIMUM_NUMBER_RANGE) );
	segments.append( nextNumber );
      }
      currentSegment++;
    }

     return segments.toString();
    
  }

  /**
   * Solves an expression of arbitrary size that consists of operators that take two arguments.
   * The one operator that doesn't take two operators is the unary "-" operator to indicate
   * a negative number. Precedence is taken into account in solving the problem. Special consideration
   * is given to equations that are undefined (ex. divide by zero). 
   *
   * @return A string representing the solution to the expression. If there is a divide by zero in the
   *         expression then the string will say "undefined".
   */
public String solveExpression( String expression ) {

	boolean done = false;
	// The current string keeps our working version of the answer.
	String current = expression;

	// The current and eventually final numerical answer.
	int result = 0;

	// The index of the next operator we have to consider. Default is 0 which will mean we
	// haven't found a real operator since all real operators need arguments on both sides of them.
	int minimumIndex = -1;
	int middle = -1;

	int left = -1;
	int right = -1;

	// while we're not done solving...
	while ( !done ) {
		
          middle = -1;
	  if ( current.contains( "*" ) || current.contains( "/" ) || current.contains( "%" )  ){

            // get the index in the string of the first occurence of these operators since java will do them
            // from left to right
            int multIndex = current.indexOf("*");
            int divIndex = current.indexOf("/");
            int modIndex = current.indexOf("%");

            // if there's a valid index for mult then pick it else take a valid index for div else take one for mod.
            middle = (multIndex != -1) ? multIndex : ( (divIndex != -1) ? divIndex : ( (modIndex != -1) ? modIndex : -1 ) ) ;

            // Let's make sure we have a valid index before going further...
            if (middle != -1) {
              // One of the indices is valid so let's make sure it's the first
              if (modIndex > -1 && multIndex > -1 && divIndex > -1) {
                  middle = Math.min(Math.min(modIndex,divIndex),multIndex);
              }
              else if ( divIndex > -1 && multIndex > -1 ) {
                  middle = Math.min(multIndex,divIndex);
              }
              else if ( modIndex > -1 && multIndex > -1 ) {
                  middle = Math.min(multIndex,modIndex);
              } 
              else if ( modIndex > -1 && divIndex > -1 ) {
                  middle = Math.min(divIndex,modIndex);
              }

              // Now perform arithmetic and check to make sure we're not dividing or mod'ing by 0
              if ( current.charAt( middle ) == '*' ) {
                left = getNumberAt(current, middle - 1);
                right = getNumberAt(current, middle + 1);
                result = left * right;
              } else if ( current.charAt( middle ) == '/' ) {
                left = getNumberAt(current, middle - 1);
                right = getNumberAt(current, middle + 1);

                // since divide by 0 is normally an error condition we're going to return
                // in the middle of the method
                if (right == 0) {
                  return "undefined";
                }

                result = left / right;

              } else if ( current.charAt( middle ) == '%' ) {
                left = getNumberAt(current, middle - 1);
                right = getNumberAt(current, middle + 1);
                // since divide/mod by 0 is normally an error condition we're going to return
                // in the middle of the method
                if (right == 0) {
                  return "undefined";
                }
                result = left % right;
              }
            }

            
	  
          } 
	  else if (current.contains( "+" ) || current.contains( "-" )){
            // next precedence
            int a = current.indexOf("+");
            int b = getFirstSubtractionIndex(current);
            middle = a;

            //NOTE: HELP ON THE MINS IN THIS SECTION WAS OBTAINED FROM GOOSE AND ADAM
            if (a > -1 && b > -1 ) {
              middle = Math.min(a,b);
            }
            else if ( b > -1 ) {
              middle = b;
            }

            if (middle != -1) {
              if ( current.charAt( middle ) == '+' ) {
                left = getNumberAt(current, middle - 1);
                right = getNumberAt(current, middle + 1);
                result = left + right;
              } else if ( current.charAt( middle ) == '-' ) {
                left = getNumberAt(current, middle - 1);
                right = getNumberAt(current, middle + 1);
                result = left - right;
              }
            }

			
	  } 
	  minimumIndex = middle;
          int leftSide = left;
	  int rightSide = right;
	  if (minimumIndex >= 1) {
	    int rightSize = new Integer(rightSide).toString().length();
	    int leftSize = new Integer(leftSide).toString().length();

	    if (minimumIndex-leftSize != 0 && minimumIndex+rightSize!=current.length()-1) {
	      current = current.substring(0, minimumIndex- leftSize ) + result  + 
		current.substring(minimumIndex + rightSize+1, current.length()); 
	    } else if (minimumIndex-leftSize != 0 ) {
	      current = current.substring(0, minimumIndex-leftSize) + result ;
	    } else if (minimumIndex+rightSize!=current.length()-1) {
	      current = result + 
		current.substring(minimumIndex +rightSize+1, current.length()); 
	    } else {
	      current = result + "";
	    }
	  }
	  
	  if (current.length() <= 1 || minimumIndex == -1 ) {
	    done = true;
	  }
	  minimumIndex = -1;
	  
	}

	return ""+result;
  }

  /**
   * Returns a number that has an element in index value. The number may
   * extend after or before the index chosen.
   *
   * @param expression The expression to get a number from.
   * @param index The index of a piece of the number.
   * @return The actual number obtained from the numeric pieces around and inside of index or
   *         0 if there was a problem with the expression and index given.
   */
  public int getNumberAt( String expression, int index ) {
    
    int number =  0;
    int startIndex = index;
    int endIndex = index;

    try {
      // find the start of the number
      for (int i=index; i>=0; i--) {
	char current = expression.charAt( i );
	if ( Character.isDigit( current ) ) {
	  startIndex = i;
	} else if (current == '-' && isUnary( expression, i ) ) {
	  startIndex = i;
	  break;
	} else {
	  break;
	}
      }

      // make sure we skip over unary op's at the beginning of a number 
      // in order to get to the end of it
      if ( expression.charAt(index) == '-' && isUnary( expression, index ) ) {
	index++;
      }

      // find the end of the number
      for (int i=index; i<expression.length(); i++) {
	if ( Character.isDigit( expression.charAt( i ) ) ) {
	  endIndex = i;
	} else {
	  break;
	}
      }

      // now pass the whole number to be parsed out by the Integer class
      number = Integer.parseInt( expression.substring(startIndex, endIndex+1) );
  
    } catch (NumberFormatException e) {
    }
    
    return number;
  }

  /**
   * This method exists since a '-' sign can be part of an actual expression or can
   * just be a unary minus sign indicating the sign of a numeric value.
   *
   * @param expression The expression to look for an index in.
   * @return The value of the zero-based index or -1 if none were found.
   */
  private int getFirstSubtractionIndex( String expression ) {
    // First we make a copy of the string soas not to change it.
    StringBuffer current = new StringBuffer( expression );

    // Get the index of the first '-' sign.
    int index = current.indexOf("-");
    
    // we have an index so check whether or not it's unary and if it is then
    // nullify it and look for another '-'
    if (index != -1) {
      while ( (index != -1) && isUnary( current.toString(), index ) ) {
	current.setCharAt(index, 'x');
	index =  current.indexOf("-");
      }
    }

    return index; 
  }

  /**
   * An index value is a unary '-' if it's the first in an expression,
   * or if there is an operator in front of it. The passed index
   * is assumed to be the index of the '-' sign to check and the
   * expression must be a valid expression.
   *
   * @return true if the '-' sign is unary and false otherwise.
   */
  private boolean isUnary( String expression, int index ) {

    boolean unary = false;

    if ( index == 0) {
      unary = true;
    } else {
      String valueBefore = expression.substring( index-1, index );
      for (int i =0; i< 5; i++) {
	if ( valueBefore.equals( "*" ) ||valueBefore.equals( "%" ) ||valueBefore.equals( "/" ) ||
	     valueBefore.equals( "+" ) ||valueBefore.equals( "-" ) ) {
	  unary = true;
	  break;
	}
      }
    }

    return unary;
  }

  /** 
   * 
   * @return True if the character is a number and false otherwise.
   */
  private boolean isNumber( char numberPiece ) {
    return (numberPiece == '0' || numberPiece == '1' || numberPiece == '2' || numberPiece == '3' ||
	numberPiece == '4' || numberPiece == '5' || numberPiece == '6' || numberPiece == '7' ||
	    numberPiece == '8' || numberPiece == '9' );
  }
}