/// <summary> /// Creates a Formula from a string that consists of a standard infix expression composed /// from non-negative floating-point numbers (using C#-like syntax for double/int literals), /// variable symbols (a letter followed by zero or more letters and/or digits), left and right /// parentheses, and the four binary operator symbols +, -, *, and /. White space is /// permitted between tokens, but is not required. /// /// Examples of a valid parameter to this constructor are: /// "2.5e9 + x5 / 17" /// "(5 * 2) + 8" /// "x*y-2+35/9" /// /// Examples of invalid parameters are: /// "_" /// "-5.3" /// "2 5 + 3" /// /// If the formula is syntacticaly invalid, throws a FormulaFormatException with an /// explanatory Message. /// </summary> public Formula(String formula) { var Operators = new List <string>(); var Values = new List <string>(); var Var = new List <string>(); IEnumerable <string> tokens = Formula.GetTokens(formula); int parcount = 0; if (formula == null) { throw new ArgumentNullException("Requires a non-null formula string."); } if (tokens.Count() == 0) { throw new FormulaFormatException("No input detected."); } string first = tokens.ElementAt <string>(0); string last = tokens.ElementAt <string>(tokens.Count() - 1); string previous = ""; //Checks first element in formula. if (Regex.IsMatch(first, rpPattern) || Regex.IsMatch(first, opPattern)) { throw new FormulaFormatException("The first element of the formula is not either a (, number, or variable."); } //Checks last element in formula. if (Regex.IsMatch(last, lpPattern) || Regex.IsMatch(last, opPattern)) { throw new FormulaFormatException("The last element of the formula is not either a ), number, or variable."); } foreach (string t in tokens) { //Checks for invalid input tokens. if (!Regex.IsMatch(t, lpPattern) && !Regex.IsMatch(t, rpPattern) && !Regex.IsMatch(t, doublePattern) && !Regex.IsMatch(t, varPattern) && !Regex.IsMatch(t, opPattern)) { throw new FormulaFormatException("Unexpected object in formula: " + t); } //Checks token for ( and adds to Operator list. if (Regex.IsMatch(t, lpPattern)) { Operators.Add(t); parcount += 1; } //Checks token for ) and adds to Operator list. if (Regex.IsMatch(t, rpPattern)) { Operators.Add(t); parcount -= 1; } //Checks token for valid operators and adds to Operator list. if (Regex.IsMatch(t, opPattern)) { Operators.Add(t); } //Checks token for numbers or variables and adds to Values list. if (Regex.IsMatch(t, doublePattern)) { Values.Add(t); } //Checks token for variables and adds to Variables list. if (Regex.IsMatch(t, varPattern) && !Regex.IsMatch(t, powpattern)) { Var.Add(t); } //Checks proper formatting following ( and operators. if (Regex.IsMatch(previous, lpPattern) || Regex.IsMatch(previous, opPattern) && !Regex.IsMatch(previous, powpattern)) { if (!Regex.IsMatch(t, varPattern) && !Regex.IsMatch(t, doublePattern) && !Regex.IsMatch(t, lpPattern)) { throw new FormulaFormatException("Check 5: Formula containing " + previous + " followed by " + t + " is invalid."); } } //Checks proper formatting following ), numbers, and variables. if (Regex.IsMatch(previous, rpPattern) || Regex.IsMatch(previous, doublePattern) || Regex.IsMatch(previous, varPattern)) { if (!Regex.IsMatch(t, opPattern) && !Regex.IsMatch(t, rpPattern)) { throw new FormulaFormatException("Check 6: `Formula containing " + previous + " followed by " + t + " is invalid."); } } //Checks relative parenthesis count. if (parcount < 0) { throw new FormulaFormatException("Parenthesis mismatch: Formula contains " + parcount + " More ) than ( ."); } previous = t; } if (parcount != 0) { throw new FormulaFormatException("Parenthesis mismatch: Formula contains More ( than ) ."); } output = formula; Variables = Var; }
/// <summary> /// Evaluates this Formula, using the Lookup delegate to determine the values of variables. (The /// delegate takes a variable name as a parameter and returns its value (if it has one) or throws /// an UndefinedVariableException (otherwise). Uses the standard precedence rules when doing the evaluation. /// /// If no undefined variables or divisions by zero are encountered when evaluating /// this Formula, its value is returned. Otherwise, throws a FormulaEvaluationException /// with an explanatory Message. /// </summary> public double Evaluate(Lookup lookup) { var Operators = new Stack <string>(); var Values = new Stack <string>(); IEnumerable <string> tokens = Formula.GetTokens(output); double FinalAnswer = 0; bool foundexp = false; if (lookup == null) { throw new ArgumentNullException("Requires a non-null lookup delegate for evaluating variables."); } //For the empty constructor case. We need it to behave like Formula("0") so evaluate should return 0. if (output == null) { return(0); } foreach (string t in tokens) { //Current item is a double. if (Regex.IsMatch(t, doublePattern) && !Regex.IsMatch(t, varPattern)) { double exp = 0; //If we found an exponential we should convert to a double then run through normal procedure. //For example, 5.0e2 should equal 500.0 . if (Regex.IsMatch(t, powpattern)) { MatchCollection matches = Regex.Matches(t, powpattern); double start = double.Parse(matches[0].Groups[1].Value); double end = double.Parse(matches[0].Groups[3].Value); exp = start * Math.Pow(10, end); foundexp = true; } if (Operators.Count == 0) { //If t is an exponential phrase, push its value. if (foundexp == true) { Values.Push(exp.ToString()); } else { Values.Push(t); } } if (Operators.Count != 0) { if (Operators.Peek() == "*" || Operators.Peek() == "/") { if (Operators.Peek() == "*") { //If current token is a exponential phrase we should use it's value. if (foundexp == true) { Values.Push(exp.ToString()); DoMultiplication(ref Values); Operators.Pop(); } else { Values.Push(t); DoMultiplication(ref Values); Operators.Pop(); } } else { //If current token is a exponential phrase we should use it's value. if (foundexp == true) { Values.Push(exp.ToString()); DoDivision(ref Values); Operators.Pop(); } else { Values.Push(t); DoDivision(ref Values); Operators.Pop(); } } } else { if (foundexp == true) { Values.Push(exp.ToString()); } else { Values.Push(t); } } } } if (Regex.IsMatch(t, varPattern)) { try { lookup(t); } catch (UndefinedVariableException) { throw new FormulaEvaluationException("Value of variable " + t + " is undefined."); } if (Operators.Count == 0) { Values.Push(lookup(t).ToString()); } if (Operators.Count != 0) { if (Operators.Peek() == "*" || Operators.Peek() == "/") { if (Operators.Peek() == "*") { Values.Push(lookup(t).ToString()); DoMultiplication(ref Values); Operators.Pop(); } else { Values.Push(lookup(t).ToString()); DoDivision(ref Values); Operators.Pop(); } } else { Values.Push(lookup(t).ToString()); } } } if (t == "+" || t == "-") { if (Operators.Count != 0) { if (Operators.Peek() == "+" || Operators.Peek() == "-") { if (Operators.Peek() == "+") { DoAddition(ref Values); Operators.Pop(); } else { DoSubtraction(ref Values); Operators.Pop(); } } } Operators.Push(t); } if (t == "*" || t == "/") { Operators.Push(t); } if (Regex.IsMatch(t, lpPattern)) { Operators.Push(t); } if (Regex.IsMatch(t, rpPattern)) { if (Operators.Peek() == "+" || Operators.Peek() == "-") { if (Operators.Peek() == "+") { DoAddition(ref Values); } else { DoSubtraction(ref Values); } Operators.Pop(); } //Always removes the ) on the top of the stack. Operators.Pop(); //Checks Operator stack for * or / and calculates if needed. if (Operators.Count != 0) { if (Operators.Peek() == "*" || Operators.Peek() == "/") { if (Operators.Peek() == "*") { DoMultiplication(ref Values); } else { DoDivision(ref Values); } Operators.Pop(); } } } foundexp = false; } if (Operators.Count == 0) { FinalAnswer = double.Parse(Values.Pop()); } if (Operators.Count == 1) { if (Operators.Peek() == "+") { DoAddition(ref Values); FinalAnswer = double.Parse(Values.Pop()); } else { DoSubtraction(ref Values); FinalAnswer = double.Parse(Values.Pop()); } } return(FinalAnswer); }
/// <summary> /// Creates a Formula from a string that consists of a standard infix expression composed /// from non-negative floating-point numbers (using C#-like syntax for double/int literals), /// variable symbols (a letter followed by zero or more letters and/or digits), left and right /// parentheses, and the four binary operator symbols +, -, *, and /. White space is /// permitted between tokens, but is not required. /// /// Examples of a valid parameter to this constructor are: /// "2.5e9 + x5 / 17" /// "(5 * 2) + 8" /// "x*y-2+35/9" /// /// Examples of invalid parameters are: /// "_" /// "-5.3" /// "2 5 + 3" /// /// If the formula is syntacticaly invalid, throws a FormulaFormatException with an /// explanatory Message. /// /// An ArgumentNullException is thrown if string is null. /// </summary> public Formula(String formula, Normalizer n, Validator v) { if (formula == null) { throw new ArgumentNullException("Parameter is Null"); } //get tokens IEnumerable <string> tokens = Formula.GetTokens(formula); //valid token patterns String opPattern = @"^[\+\-*/]$"; Regex operation = new Regex(opPattern); String varPattern = @"^[a-zA-Z][0-9a-zA-Z]*$"; Regex variable = new Regex(varPattern); String spacePattern = @"^\s+$"; Regex space = new Regex(spacePattern); //counters int opening = 0; int closing = 0; int elementCounter = 0; String previous = null; //keep track of normalized variables normalized_vars = new HashSet <string>(); //check if the there are any tokens to check if (tokens.Count <string>() == 0) { throw new FormulaFormatException("No tokens"); } //check if the formua meets the syntactic standards foreach (string element in tokens) { elementCounter++; //check if there is at least one token if (element == null) { throw new FormulaFormatException("No tokens"); } double number; //check for all valid tokens if (!(element.Equals("(") || element.Equals(")") || operation.IsMatch(element) || variable.IsMatch(element) || Double.TryParse(element, out number) || space.IsMatch(element))) { throw new FormulaFormatException("Invalid Tokens"); } //check if the number of closing parathensis is ever greater than the number of openeing parathensis if (element.Equals("(")) { opening++; } if (element.Equals(")")) { closing++; } if (closing > opening) { throw new FormulaFormatException("More closing than opening"); } if (elementCounter == tokens.Count <string>()) { //check if The total number of opening parentheses must equal the total number of closing parentheses. if (opening != closing) { throw new FormulaFormatException("closing and opening do not equal"); } } //check if The first token of a formula must be a number, a variable, or an opening parenthesis. if (!(tokens.First().Equals("(") || variable.IsMatch(tokens.First()) || Double.TryParse(tokens.First(), out number))) { throw new FormulaFormatException("First token was not a number, variable, or opening parenthesis"); } //check if Any token that immediately follows an opening parenthesis or an operator must be either a number, //a variable, or an opening parenthesis. if (previous != null) { if (previous.Equals("(") || operation.IsMatch(previous)) { if (!(variable.IsMatch(element) || Double.TryParse(element, out number) || element.Equals("("))) { throw new FormulaFormatException("token immediatly following an open parethesis or operater was not a number, variable, or open parenthesis"); } } if (element.Equals("(") || operation.IsMatch(element)) { if (element == tokens.Last <string>()) { throw new FormulaFormatException("The last element can not be an open parathesis or an operator"); } } //check if Any token that immediately follows a number, a variable, or a closing parenthesis must be either //an operator or a closing parenthesis. if (Double.TryParse(previous, out number) || variable.IsMatch(previous) || previous.Equals(")")) { if (!(operation.IsMatch(element) || element.Equals(")"))) { throw new FormulaFormatException("The token immediatlely folloeing a number, varibale, or colsing parenthesis was not either an operator or closing parethesis"); } } } //change the previous reference previous = element; } //if all syntactical arrors pass then put the tokens in a string array to be accessed by other methods int stringCounter = 0; stringFormula = new String[tokens.Count()]; foreach (string element in tokens) { //check if the element is a variable if (variable.IsMatch(element)) { if (!(variable.IsMatch(n.Invoke(element)))) { throw new FormulaFormatException("normalized variable could not be recognized as a variable"); } if (!(v.Invoke(n.Invoke(element)))) { throw new FormulaFormatException("the normalized varible was not valid"); } //add the normalized variable to the array stringFormula[stringCounter] = n.Invoke(element); //keep track of the normalized variables normalized_vars.Add(n.Invoke(element)); } stringFormula[stringCounter] = element; stringCounter++; } }