一次重构 (算术表达式求值问题)

    最近工作比较忙,很久没写东西了,今天突然有再写一写的冲动,于是就有了这篇^_^

    三个月之前,我曾经写了一篇关于算术表达式求值的文章,当时是单纯从算法的角度考虑整个问题的,而没有考虑到软件的扩展性,三个月以后,
我重新考虑了这个问题,怎么让这个微型的程序具备扩展性呢?空闲时间看了不少 SharpDevelop 的源码,对其中的插件实现机制当然会有一定印
象,那么何不用插件的形式来实现下这个程序呢?这样就可以在将来需要新的运算法则时方便地扩展,而不必更改已经存在的程序,那么就简单地设
计一下吧(其实我不太喜欢使用“设计”一词的,因为貌似这个词已经泛滥了,但是此处貌似也没有别的词来代替)。

    废话了这一通,不知道各位看客有没有已经想吐了,那就开始吧。首先是弄个接口出来吧:

// Output.cs
using System;

namespace CnBlogs.Youzai.CalculatorCodonInterface {
    
public class Output {
        
private double result;
        
private string errMsg;

        
public Output(double result, string errMsg) {
            
this.result = result;
            
this.errMsg = errMsg;
        }


        
public double Result {
            
get 
                
return this.result; 
            }

            
set {
                
this.result = value;
            }

        }


        
public string ErrMsg {
            
get {
                
return this.errMsg;
            }

            
set {
                
this.errMsg = value;
            }

        }

    }

}

  // IExpressionCalculator.cs
  using System;
using System.Collections.Generic;

namespace CnBlogs.Youzai.CalculatorCodonInterface {
    
public interface IExpressionCalculator {
        
char Operator {
            
get;
        }


        
int Priority {
            
get;
        }


        
bool IsRightOrder {
            
get;
        }


        IList
<double> Input {
            
get;
        }


        Output Result 
{
            
get;
        }


        
bool Calculate();
    }

}

算法问题早写过了(参考 使用传统算法进行表达式求值), 这里就不重复了. 现在实现一个简单的算子, 也就是实现 接口 IExpressionCalculator
这里就实现一个加法运算吧

// AddCalculatorCodon.cs
using System;
using System.Collections.Generic;
using CnBlogs.Youzai.CalculatorCodonInterface;

namespace CnBlogs.Youzai.BasicCalculatorCodons {
    
public class AddCalculatorCodon : IExpressionCalculator {
        
private Output result = new Output(0string.Empty);
        
private IList<double> input = new List<double>();

        
IExpressionCalculator Members
    }

}

这里的 Priority 设置需要注意, 相同优先级的运算符应该设置为相同, 还有要留有一定的余地, 比如加减可以设为 0,乘除为10, 这样就可以确保将来
扩展时不至没有可用的 Priority. 其他的运算实现方法相似, 这里就不写了。把每一个算子编译成单独的程序集,然后用反射的方式加载并实例化。当
然也可以把类似的运算编译在同一个程序集里。

下面是程序集加载和算子实例化以及算法实现,不多解释,直接给出代码好了。

// CalculatorManager.cs
using System;
using System.IO;
using System.Reflection;
using System.Collections.Generic;
using CnBlogs.Youzai.CalculatorCodonInterface;

namespace CnBlogs.Youzai.ExpressionCalculator {
    
public class CalculatorManager {
        
private static CalculatorManager instance;
        
private IDictionary<char, IExpressionCalculator> addinMap;

        
static CalculatorManager() {

            instance 
= new CalculatorManager();
        }


        
public static CalculatorManager Instance {
            
get {
                
return instance;
            }

        }


        
private CalculatorManager() {
            
this.addinMap = new Dictionary<char, IExpressionCalculator>();
            
string path = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
            path 
= Path.Combine(path, "AddIns");

            
if (Directory.Exists(path)) {
                
// Builder the AddIn trees
                foreach (string filename in Directory.GetFiles(path, "*.dll", SearchOption.AllDirectories)) {
                    Assembly assembly 
= null;
                    
try {
                        assembly 
= Assembly.LoadFrom(filename);
                    }
 catch {
                        assembly 
= null;
                    }


                    
if (assembly != null{
                        
foreach (Type type in assembly.GetTypes()) {
                            Type interfaceType 
= typeof(IExpressionCalculator);
                            
if (interfaceType != type && interfaceType.IsAssignableFrom(type)) {
                                
try {
                                    IExpressionCalculator Calculator 
=
                                        (IExpressionCalculator)assembly.CreateInstance(type.FullName);
                                    
this.addinMap.Add(Calculator.Operator, Calculator);
                                }
 catch {
                                }

                            }

                        }

                    }

                }

            }

        }


        
public bool Calculate(string infixExpression, out double result, out string errorExpression) {
            result 
= 0;
            errorExpression 
= string.Empty;
            infixExpression 
= infixExpression.Replace(" "string.Empty);
            
if (infixExpression.EndsWith("=")) {
                infixExpression 
= infixExpression.Substring(0, infixExpression.Length - 1);
            }


            List
<char> validCharList = new List<char>();
            
for (char ch = '0'; ch <= '9'; ch++{
                validCharList.Add(ch);
            }


            validCharList.Add(
'.');
            validCharList.Add(
'e');
            validCharList.Add(
'E');
            validCharList.Add(
'(');
            validCharList.Add(
')');
            validCharList.AddRange(
this.addinMap.Keys);

            
foreach (char ch in infixExpression) {
                
if (!validCharList.Contains(ch)) {
                    errorExpression 
= "This are ilegal characters in the expression";
                    
return false;
                }

            }


            
for (int i = 0; i < infixExpression.Length; i++{
                
char ch = infixExpression[i];
                
if (this.addinMap.ContainsKey(ch) && this.addinMap[ch].IsRightOrder) {
                    
int j = i + 1;
                    
if (j < infixExpression.Length) {
                        
char nextChar = infixExpression[j];
                        
if (nextChar == '-'{
                            infixExpression 
= infixExpression.Insert(j, "(0");
                            i 
+= 2;
                            j 
+= 2;
                            
int k = j + 1;
                            
for (; k < infixExpression.Length; k++{
                                
if (!char.IsNumber(infixExpression[k]) && infixExpression[k] != '.'{
                                    
break;
                                }

                            }

                            infixExpression 
= infixExpression.Insert(k, ")");
                        }

                    }

                }

            }


            Queue
<string> postfixQueue = GetPostfixQueue(infixExpression);
            Stack
<double> stackOperand = new Stack<double>();

            
while (postfixQueue.Count != 0{
                
string expression = postfixQueue.Dequeue();
                
if (IsOperator(expression)) {
                    
if (stackOperand.Count < 2{
                        errorExpression 
= "Ilegal expressions";
                        
return false;
                    }

                    
double operand1 = stackOperand.Pop();
                    
double operand2 = stackOperand.Pop();

                    
if (Calculate(operand2, operand1, expression[0])) {
                        stackOperand.Push(
this.addinMap[expression[0]].Result.Result);
                    }
 else {
                        
return false;
                    }

                }
 else {
                    
double resultTmp;
                    
if (double.TryParse(expression, out resultTmp)) {
                        stackOperand.Push(resultTmp);
                    }
 else {
                        errorExpression 
= string.Format("Can't convert {0} to number", resultTmp);
                        
return false;
                    }

                }

            }


            
if (stackOperand.Count == 1{
                result 
= stackOperand.Pop();
                
return true;
            }


            errorExpression 
= "Ilegal expressions";
            
return false;
        }


        
private bool Calculate(double operand1, double operand2, char chOperator) {

            
if (this.addinMap.ContainsKey(chOperator)) {
                IExpressionCalculator calculator 
= this.addinMap[chOperator];
                calculator.Input.Clear();
                calculator.Input.Add(operand1);
                calculator.Input.Add(operand2);
                
if (calculator.Calculate()) {
                    
return true;
                }

            }


            
return false;
        }


        
private Queue<string> GetPostfixQueue(string infixExpression) {
            Queue
<string> postfixQueue = new Queue<string>();
            Stack
<char> stack = new Stack<char>();
            
if (infixExpression[0== '-'{
                infixExpression 
= "0" + infixExpression;
            }


            IList
<char> validCharList = new List<char>();
            
for (char i = '0'; i <= '9'; i++{
                validCharList.Add(i);
            }

            validCharList.Add(
'.');
            validCharList.Add(
'e');
            validCharList.Add(
'E');

            
while (infixExpression != string.Empty) {
                
char ch = infixExpression[0];

                
if (validCharList.Contains(ch)) {
                    
string oprand = string.Empty;

                    
while (validCharList.Contains(infixExpression[0])) {
                        oprand 
+= infixExpression[0].ToString();
                        infixExpression 
= infixExpression.Substring(1);
                        
if (infixExpression == string.Empty) {
                            
break;
                        }

                    }

                    postfixQueue.Enqueue(oprand);
                }
 else if (IsOperator(ch.ToString())) {
                    
if (stack.Count > 0{
                        
char chOperator = stack.Peek();
                        
while (!(chOperator == '(' || ComparePriority(chOperator, ch) < 0 ||
                            (ComparePriority(chOperator, ch) 
== 0 && this.addinMap[chOperator].IsRightOrder)) && stack.Count > 0{
                            
char chOther = stack.Pop();
                            postfixQueue.Enqueue(chOther.ToString());
                            
if (stack.Count < 1{
                                
break;
                            }

                            chOperator 
= stack.Peek();
                        }

                    }


                    stack.Push(ch);
                    infixExpression 
= infixExpression.Substring(1);
                }
 else if (ch == '('{
                    stack.Push(ch);
                    infixExpression 
= infixExpression.Substring(1);
                }
 else if (ch == ')'{
                    
char chOperator = stack.Pop();
                    
while (chOperator != '(' && stack.Count > 0{
                        postfixQueue.Enqueue(chOperator.ToString());
                        chOperator 
= stack.Pop();
                    }

                    infixExpression 
= infixExpression.Substring(1);
                }

            }


            
while (stack.Count > 0{
                
char ch = stack.Pop();
                
if (ch != '('{
                    postfixQueue.Enqueue(ch.ToString());
                }

            }


            
return postfixQueue;
        }


        
private int ComparePriority(char chOperator, char otherOperator) {
            
if (this.addinMap.ContainsKey(chOperator) && this.addinMap.ContainsKey(otherOperator)) {
                
return this.addinMap[chOperator].Priority - this.addinMap[otherOperator].Priority;
            }


            
throw new NotSupportedException(string.Format("Not supported operators: {0} and {1}", chOperator, otherOperator));
        }


        
private bool IsOperator(string str) {
            IList
<string> list = new List<string>();
            
foreach (char ch in this.addinMap.Keys) {
                list.Add(ch.ToString());
            }


            
return list.Contains(str);
        }

    }

}

    CalculatorManager 是一个 Singleton 类,使用应该方便的,UI 实现起来很简单,这里就不写了。

    将来有新的运算需要添加时,只需要实现 IExpressionCalculator 接口,编译成单独的程序集,所以放在 .\AddIns\目录下面就可以了,
程序启动时会自动搜索
现在你就有一个可扩展的表达式求值运算器了。


posted on 2008-04-26 20:50  优哉@游哉  阅读(543)  评论(0编辑  收藏  举报

导航