2011-07-27 51 views
5

我一直在实现一个增强型Shunting-Yard算法来解析算术表达式。该算法的一个方面是维护QueueStack类型安全的方法在集合中存储两种类型的对象

在我的实施中,Queue包含ExpressionsOperatorsStack包含OperatorsParenthesis

Expressions,ParenthesisOperators没有任何共同之处,它们保证它们中的任何两个具有共享接口。

途径:

  • 我目前实现由ExpressionOperator实施INotParanthesisOperatorParanthesis执行INotExpression。然后我宣布Queue <INotParanthesis>Stack <INotExpression>

    我不喜欢这个实现 - 这些接口对于更干净的算法代码来说似乎非常黑客。我也相信接口应该描述一个对象是什么,而不是它不是。

  • 另一方面,我也不想使用<Object>的集合,因为可能很难确定这些代码的正确性。

  • 到目前为止,我唯一想到的就是实现我自己的NonParanthesisQueueNonExpressionStack容器。这具有对从这些容器中拉出的对象进行更一致的类型检查的优点 - 以及更多代码的缺点。

是否有任何合理的替代方案我的方法?

回答

4

这听起来像你真正想要的是和类型。虽然C#没有内置这些​​内容,但函数式编程还有一个技巧可以使用,称为Church编码来实现这一点。它完全是类型安全的,不涉及任何类型的转换,但是由于类型推断的局限性,在C#中使用它有点奇怪。

主要技巧是,不是使用属性和检查来检索两个选项之一,我们有一个高阶函数Map,它接受两个函数作为参数,并根据所存在的替代方法调用相应的函数。这里是你将如何使用它:

var stack = new Stack<IEither<Operator, Parenthesis>>(); 

stack.Push(new Left<Operator, Parenthesis>(new Operator())); 
stack.Push(new Right<Operator, Parenthesis>(new Parenthesis())); 

while (stack.Count > 0) 
{ 
    stack.Pop().Map(op => Console.WriteLine("Found an operator: " + op), 
        par => Console.WriteLine("Found a parenthesis: " + par)); 
} 

这里是IEitherLeftRight实施。它们是完全通用的,可用于任何你想要求和类型的地方。

public interface IEither<TLeft, TRight> 
{ 
    TResult Map<TResult>(Func<TLeft, TResult> onLeft, Func<TRight, TResult> onRight); 
    void Map(Action<TLeft> onLeft, Action<TRight> onRight); 
} 

public sealed class Left<TLeft, TRight> : IEither<TLeft, TRight> 
{ 
    private readonly TLeft value; 

    public Left(TLeft value) 
    { 
     this.value = value; 
    } 

    public TResult Map<TResult>(Func<TLeft, TResult> onLeft, Func<TRight, TResult> onRight) 
    { 
     return onLeft(value); 
    } 

    public void Map(Action<TLeft> onLeft, Action<TRight> onRight) 
    { 
     onLeft(value); 
    } 
} 

public sealed class Right<TLeft, TRight> : IEither<TLeft, TRight> 
{ 
    private readonly TRight value; 

    public Right(TRight value) 
    { 
     this.value = value; 
    } 

    public TResult Map<TResult>(Func<TLeft, TResult> onLeft, Func<TRight, TResult> onRight) 
    { 
     return onRight(value); 
    } 

    public void Map(Action<TLeft> onLeft, Action<TRight> onRight) 
    { 
     onRight(value); 
    } 
} 

参考文献:

+0

谢谢!这正是我所期待的。 – Vladislav

+0

@Vladislav这与类似于理查尔的答案(基本上这个想法是使用元组类型来保存异类数据),但我想你可以结合两者的想法使它更短(我说这是因为你喜欢它不详细)。 – nawfal

1

也许你可以为每一个定义一个小的持有者类型,一个带有Expression属性和一个操作符属性,另一个带有一个操作符属性和一个括号属性。访问者和构造函数可以声明或以其他方式确保只有一个被填充。队列和堆栈将分别包含适当的持有者类型。

有点尴尬,但typeafe和可行。

希望有人会有一个更聪明的想法。

+0

谢谢 - 那是不是我考虑的 - 肯定是有道理的,尽管是利特详细。 – Vladislav

0

如果名字困扰你,IQueueable和IStackable如何?以下是一些与你的相似的其他stackoverflow问题(关于标记接口)。

What is the purpose of a marker interface?

Interface without any members - bad practice?

+0

我的担心是我为这些类提供了接口,仅用于实现此算法。关于表情本身并没有什么不可拆卸的地方,关于括号也没有内在的不可分割的地方。如果我改变我的分析算法,这些接口将只是在课堂上的重量。 – Vladislav

相关问题