/// <summary> /// Compute the numeric value of the expression /// </summary> /// <param name="reducedExpressionElement"></param> /// <returns>The value of the expression element</returns> public string Compute(ExpressionElement reducedExpressionElement) { string expression = reducedExpressionElement.ReducedExpression; // break up into numbers and operators // (a naive alternative to using something like Polish notation string[] operators = _expressionConfig.ExtractOperators(expression); string[] numbers = _expressionConfig.ExtractNumerands(expression); // if 1st op is a leading unary, apply it and discard if (_expressionConfig.UnaryOpChars.Contains(expression[0])) { numbers[0] = _expressionConfig.UnaryOperations[expression[0].ToString()](numbers[0]); operators = operators.Skip(1).ToArray(); } // This handles case where input is a simple number if (numbers.Length == 1) return numbers[0]; foreach (char[] opersToProcess in _expressionConfig.OperatorsByPrecedence) { while (operators.Any(t => t.IndexOfAny(opersToProcess) == 0)) { List<string> remainingOperators = new List<string>(); List<string> remainingNums= new List<string>(); for (int operIdx = 0; operIdx < operators.Length; operIdx++) { // when we find something to process, mutate arrays and start over .... // TODO Operator processing feels klunky if (operators[operIdx].IndexOfAny(opersToProcess) == 0) { var computedValue = Compute(numbers[operIdx], numbers[operIdx + 1], operators[operIdx]); remainingOperators.AddRange(operators.Skip(operIdx+1)); remainingNums.Add(computedValue); remainingNums.AddRange(numbers.Skip(operIdx+2)); break; } remainingOperators.Add(operators[operIdx]); remainingNums.Add(numbers[operIdx]); } operators = remainingOperators.ToArray(); numbers = remainingNums.ToArray(); } } if (numbers.Length != 1 && operators.Length != 0) throw new Exception($"Error during compute: {expression}, numbers: {numbers.Length}; operators: {operators.Length}"); return reducedExpressionElement.Unary.HasValue ? ComputeUnary(reducedExpressionElement.Unary.ToString(), numbers[0]) : numbers[0]; }
// Reduce helpers ... public string Evaluate(ExpressionElement reducedExpressionElement) { return reducedExpressionElement.HadParens ? Compute(reducedExpressionElement) : reducedExpressionElement.ReducedExpression; }
/// <summary> /// Recusively decompose an expression into a tree of ExpressionElements /// </summary> /// <param name="expressionElement"></param> /// <param name="level"></param> /// <returns>The ExpressionElement with the SubExpressionElements property populated</returns> private ExpressionElement Decompose(ExpressionElement expressionElement, int level = 0) { IEnumerable<ExpressionElement> expressionElementChildren = FindExpressionsAtSameLevel(expressionElement.Expression, level); expressionElement.PopulateSubExpressionElements(expressionElementChildren.Select(e => Decompose(e, level + 1))); return expressionElement; }
/// <summary> /// Recusively process an ExpressionElement to compute its "reduced" value /// i.e. an expression with all parenthetical expressions computed /// </summary> /// <param name="expressionElement"></param> /// <returns></returns> private ExpressionElement Reduce(ExpressionElement expressionElement) { if (!expressionElement.HasChildren()) { expressionElement.ReducedExpression = expressionElement.Expression; } else { ExpressionElement[] itsReducedChildren = expressionElement.SubExpressionElements.Select(Reduce).ToArray(); StringBuilder reducedExpression = new StringBuilder(); foreach (ExpressionElement reducedChild in itsReducedChildren) { reducedExpression.Append(Evaluate(reducedChild) + reducedChild.JoinOp); } expressionElement.ReducedExpression = reducedExpression.ToString(); } return expressionElement; }
/// <summary> /// Produce a processed \ExpressionElement with either its value (if its valid) or with relevant errors if it is not /// </summary> /// <param name="minifiedExpression">A validated, compressed expression produced by the IExpressionValidator</param> /// <returns>A processed ExpressionElement</returns> public ExpressionElement Process(string minifiedExpression) { ExpressionElement expressionElement = new ExpressionElement(minifiedExpression, 0, 0); // catch exceptions here after potentially recursive calls have been allowed to unwind ... try { expressionElement = Decompose(expressionElement); } catch (Exception e) { throw new Exception("Processing exception [Decompose]", e); } try { expressionElement = Reduce(expressionElement); } catch (Exception e) { throw new Exception("Processing exception [Reduce]", e); } return expressionElement; }