一次重构 (算术表达式求值问题)
最近工作比较忙,很久没写东西了,今天突然有再写一写的冲动,于是就有了这篇^_^
三个月之前,我曾经写了一篇关于算术表达式求值的文章,当时是单纯从算法的角度考虑整个问题的,而没有考虑到软件的扩展性,三个月以后,
我重新考虑了这个问题,怎么让这个微型的程序具备扩展性呢?空闲时间看了不少 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.csusing 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;
}
}
}
}
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(0, string.Empty);
private IList<double> input = new List<double>();
IExpressionCalculator Members
}
}
这里的 Priority 设置需要注意, 相同优先级的运算符应该设置为相同, 还有要留有一定的余地, 比如加减可以设为 0,乘除为10, 这样就可以确保将来
扩展时不至没有可用的 Priority. 其他的运算实现方法相似, 这里就不写了。把每一个算子编译成单独的程序集,然后用反射的方式加载并实例化。当
然也可以把类似的运算编译在同一个程序集里。
下面是程序集加载和算子实例化以及算法实现,不多解释,直接给出代码好了。
using System;
using System.Collections.Generic;
using CnBlogs.Youzai.CalculatorCodonInterface;
namespace CnBlogs.Youzai.BasicCalculatorCodons {
public class AddCalculatorCodon : IExpressionCalculator {
private Output result = new Output(0, string.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\目录下面就可以了,
程序启动时会自动搜索现在你就有一个可扩展的表达式求值运算器了。
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\目录下面就可以了,
程序启动时会自动搜索现在你就有一个可扩展的表达式求值运算器了。
这是微软技术的一贯特点,使用简单。但是如果要深入的话,还是要投入不少精力的