using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; using System.Text.RegularExpressions; namespace iBemsDataService.Util { public class MathCalculator { public enum Parameter { A = 0 , B , C , D , E , F , G , H , I , J , K , L , M , N , O , P , Q , R , S , T , U , V , W , X , Y , Z } public enum OperatorType { Plus, Minus, Multiplication, Division, OpenBrace } public delegate decimal CalculateFunctionDelegate( decimal inputValue ); public Dictionary Parameters = new Dictionary(); public Dictionary Functions = new Dictionary(); private List OperationOrder = new List(); private static Regex doubleRegex = new Regex( @"^-?\d+(?:\.\d+)?" , RegexOptions.Compiled ); private static Regex functionRegex = new Regex( @"^\$(.+?)\((.)\)\$" , RegexOptions.Compiled ); private String Fomula { get; set; } Stack stack = new Stack(); List CompliedFormula = new List(); public void TracePostFormula() { Trace.Write( "Formula : " ); foreach( var item in this.CompliedFormula ) { Trace.Write( item ); Trace.Write( " " ); } Trace.WriteLine( "" ); } public void SetParameter( MathParameterValue[] parameters ) { foreach( var p in parameters ) { this.Parameters.Add( p.Variable , p ); } //var pv = new ParameterValue { Variable = p, Value = value }; //this.Parameters.Add( p , pv ); MapParameters(); // return pv; } public void SetFunction( string functionName, CalculateFunctionDelegate callback) { if( this.Functions.ContainsKey( functionName ) ) { this.Functions[functionName] = callback; return; } this.Functions.Add( functionName , callback ); //var pv = new ParameterValue { Variable = p, Value = value }; //this.Parameters.Add( p , pv ); MapFunctions(); // return pv; } public List GetFunctions() { var list = new List(); foreach( var item in this.CompliedFormula ) { switch( item.Type ) { case FormulaItem.ItemType.Function: list.Add( (FunctionItem)item ); break; } } return list; } private void MapFunctions() { foreach( var item in this.CompliedFormula ) { switch( item.Type ) { case FormulaItem.ItemType.Function: var f = item as FunctionItem; f.Function.FunctionDelegate = this.Functions[f.Function.FunctionName]; break; } } } private void MapParameters() { foreach( var item in this.CompliedFormula ) { switch( item.Type ) { case FormulaItem.ItemType.OperandField: var o = item as OperandFieldItem; o.ParameterValue = this.Parameters[o.ParameterValue.Variable]; break; case FormulaItem.ItemType.Function: var f = item as FunctionItem; switch( f.Value.Type ) { case FormulaItem.ItemType.OperandField: var of = (OperandFieldItem)f.Value; of.ParameterValue = this.Parameters[of.ParameterValue.Variable]; break; } break; } } } public decimal? Calculate() { Stack stack = new Stack(); foreach( var item in this.CompliedFormula ) { switch( item.Type ) { case FormulaItem.ItemType.Constant: case FormulaItem.ItemType.OperandField: case FormulaItem.ItemType.Function: stack.Push(item); break; case FormulaItem.ItemType.Operator: var ci = this.CalculateOperator( stack.Pop() , stack.Pop() , ((OperatorItem)item).Operator ); if( ci == null ) { return null; } stack.Push( ci ); // Trace.WriteLine( ci.GetValue() ); break; } } return stack.Pop().GetValue(); } private ConstantItem CalculateOperator( FormulaItem item1 , FormulaItem item2 , OperatorType op ) { decimal result = 0; // Trace.WriteLine( item2.GetValue() + " " + op + " " + item1.GetValue() ); switch( op ) { case OperatorType.Plus: result = item2.GetValue() + item1.GetValue(); break; case OperatorType.Minus: result = item2.GetValue() - item1.GetValue(); break; case OperatorType.Multiplication: result = item2.GetValue() * item1.GetValue(); break; case OperatorType.Division: if( item1.GetValue() == 0 ) return null; result = item2.GetValue() / item1.GetValue(); break; } return new ConstantItem { Value = result }; } public bool Compile( string formula ) { this.CompliedFormula.Clear(); formula = formula.Replace( " ", "" ); Stack stack = new Stack(); char[] braces = new char[] { '(' , ')' }; string operators = "+-*/"; var array = formula.ToCharArray(); for( var i = 0 ; i < array.Length ; ) { var item = array[i]; switch( item ) { case '(': stack.Push( new OperatorItem { Operator = OperatorType.OpenBrace } ); i++; break; case ')': while( stack.Any() ) { var stackItem = stack.Pop(); if( stackItem.Operator == OperatorType.OpenBrace ) { break; } CompliedFormula.Add( stackItem ); //Trace.WriteLine( stackItem.Operator ); } i++; break; case '$': Match funcMatch = functionRegex.Match( formula.Substring( i ) ); if( funcMatch.Success == false || funcMatch.Groups.Count != 3 ) { CompliedFormula.Clear(); return false; } Trace.WriteLine( funcMatch.Groups[0].Value ); Trace.WriteLine( funcMatch.Groups[1].Value ); Trace.WriteLine( funcMatch.Groups[2].Value ); var f = new FunctionItem { Function = new MathFunction { FunctionName = funcMatch.Groups[1].Value } }; string value = funcMatch.Groups[2].Value; if( 'A' <= value[0] && value[0] <= 'Z' ) { if( value.Length > 1 ) { Trace.WriteLine( "function parameter wrong!" ); return false; } f.Value = new OperandFieldItem { ParameterValue = new MathParameterValue { Variable = (Parameter)(value[0] - 'A') } }; } else { decimal outputValue; if( decimal.TryParse( value, out outputValue ) == false ) { return false; } f.Value = new ConstantItem { Value = outputValue }; } CompliedFormula.Add( f ); i += funcMatch.Length; break; default: int operatorIndex = operators.IndexOf(item); bool isProcessed = false; if( operatorIndex == (int)OperatorType.Minus ) { if( i == 0 || array[i-1] == '(' || operators.IndexOf(array[i-1]) >= 0 ) { isProcessed = true; } } if( isProcessed == false ) { if( operatorIndex >= 0 ) { //if( operatorIndex < (int)OperatorType.Multiplication ) { while( stack.Any() ) { var stackItem = stack.Peek(); if( stackItem.Operator == OperatorType.OpenBrace ) break; if( (int)stackItem.Operator < operatorIndex ) break; stack.Pop(); CompliedFormula.Add( stackItem ); } } stack.Push( new OperatorItem { Operator = (OperatorType)operatorIndex } ); i++; break; } else if( 'A' <= item && item <= 'Z' ) { //Trace.WriteLine( item ); CompliedFormula.Add( new OperandFieldItem { ParameterValue = new MathParameterValue { Variable = (Parameter)(item - 'A') } } ); i++; break; } } Match match = doubleRegex.Match(formula.Substring( i )); if( match.Success == false ) { throw new Exception( "Error: Check Syntax : " + item ); } decimal parseValue = decimal.Parse( match.Value ); //Trace.WriteLine( value ); CompliedFormula.Add( new ConstantItem { Value = parseValue } ); i += match.Value.Length; break; } } while( stack.Any() ) { var operatorItem = stack.Pop(); //Trace.WriteLine( operatorItem.Operator ); CompliedFormula.Add( operatorItem ); } return true; } public bool ExtractDouble( string inputValue, out decimal output ) { return decimal.TryParse( inputValue, out output); } public abstract class FormulaItem { public enum ItemType { Operator, Constant, OperandField, Function } public ItemType Type { get; set; } public abstract decimal GetValue(); } public class OperatorItem : FormulaItem { public OperatorType Operator { get; set; } public OperatorItem() { this.Type = FormulaItem.ItemType.Operator; } public override string ToString() { switch( this.Operator ) { case OperatorType.Plus: return "+"; case OperatorType.Minus: return "-"; case OperatorType.Multiplication: return "*"; case OperatorType.Division: return "/"; } return "Unknown"; } public override decimal GetValue() { throw new Exception( "OperatorItem.GetValue() Called!" ); //return 0; } } public class ConstantItem : FormulaItem { public decimal Value { get; set; } public ConstantItem() { this.Type = FormulaItem.ItemType.Constant; } public override string ToString() { return this.Value.ToString(); } public override decimal GetValue() { return this.Value; } } public class OperandFieldItem : FormulaItem { public MathParameterValue ParameterValue { get; set; } public OperandFieldItem() { this.Type = FormulaItem.ItemType.OperandField; } public override string ToString() { return this.ParameterValue.Variable.ToString(); } public override decimal GetValue() { return this.ParameterValue.Value; } } public class FunctionItem : FormulaItem { public MathFunction Function { get; set; } public FormulaItem Value { get; set; } public FunctionItem() { this.Type = FormulaItem.ItemType.Function; } public override string ToString() { return string.Format( "Function({0})" , this.Value.GetValue() ); } public override decimal GetValue() { return this.Function.FunctionDelegate( this.Value.GetValue() ); } } } public class MathFunction { public String FunctionName { get; set; } public MathCalculator.CalculateFunctionDelegate FunctionDelegate { get; set; } } public class MathParameterValue { public MathCalculator.Parameter Variable { get; set; } public decimal Value { get; set; } } }