用于逻辑表达式解析和评估的更好的类结构

时间:2016-07-17 13:51:32

标签: c#

以下是关于如何解释此问题的后续问题。结果证明它比公认的答案更强大:Extract All Possible Paths from Expression-Tree and evaluate them to hold TRUE

我在运行时从字符串中获得了一些逻辑表达式:

"100 AND 101"
"102 AND 103 AND 104 AND 105"
"(106 AND 107 AND 108) OR (109 AND 110)"
"111 AND (NOT( 112 AND 113 ))" // NOT is encapsulated in its own group if it is combined

操作类型为:

  • AND用于连接/ logical-and
  • OR用于disjunction / logical-或
  • NOT for inversion / logical.not
  • 用括号分组

操作数是一些Id表示为整数。

我假设表达式字符串为:

  • 没有错误
  • 没有分组的分离和连词的交错(这是无效的:100 AND 101 OR 102 AND 103

如果此表达式的计算结果为truefalse,我需要解析此字符串并根据ID列表进行检查。 我目前的结构是:

public class ExpressionTree
{
    public enum OperationTypes { Not , And , Or }

    public static Dictionary<string , OperationTypes> OperationTypeMap = new Dictionary<string , OperationTypes> {
        { "NOT" , OperationTypes.Not } ,
        { "AND" , OperationTypes.And } ,
        { "OR"  , OperationTypes.Or  } ,
    };

    public class Group
    {
        public OperationTypes OperationType;
        public Group Parent;

        public List<Group> Groups = new List<Group>();
        public List<Identifier> Identifiers = new List<Identifier>();
    }

    public class Identifier
    {
        public OperationTypes OperationType;
        public Group Parent;

        public int Id;
    }

    public Group Root;
}

然而,这种结构并不像我希望的那样直观,而且它的评价也很麻烦。 主要焦点应该是易于理解,性能不是一个问题。

这种表达式是否有更优化的类结构更容易解释?

这是一个工作示例(可能还有错误)

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;


namespace VisitorPattern
{
    public class Test
    {
        public static void Main2( string[] args )
        {
            var list = new Dictionary<string , List<int>> {
                { "({100} OR {101} OR {102})" ,                                             new List<int> { 100 } } ,
                { "(NOT ({100} OR {101} OR {102}))" ,                                       new List<int> { 0 } } ,
                { "{100} AND {101} AND {102}" ,                                             new List<int> { 100 , 101 , 102 } } ,
                { "{100} OR {101} OR {102}" ,                                               new List<int> { 101 } } ,
                { "NOT ({100})" ,                                                           new List<int> { 0 } } ,
                { "{100} AND (NOT ({101} OR {102} OR {103}))" ,                             new List<int> { 100 , 104 } } ,
                { "({100} AND ({101} OR {102} OR {103})) AND ({104} OR {105} OR {106})" ,   new List<int> { 100 , 103 , 104 } } ,
            };

            foreach( var elem in list )
            {
                var processor = new Processor();
                var ruleVisitor = new RuleVisitor { Rules = elem.Value };
                var node = processor.Parse( elem.Key );
                var result = ruleVisitor.Visit( node , true );

                Debug.Assert( result );
            }
        }
    }

    public interface IVisitor<T>
    {
        T Visit( OrNode node , bool result );
        T Visit( NotNode node , bool result );
        T Visit( AndNode node , bool result );
        T Visit( GroupNode node , bool result );
        T Visit( IdentifierNode node , bool result );
    }

    public class RuleVisitor : IVisitor<bool>
    {
        public List<int> Rules;

        public bool Visit( OrNode node , bool result )
        {
            return node.SubNodes.First().Accept( this , result ) || result;
        }

        public bool Visit( AndNode node , bool result )
        {
            return node.SubNodes.First().Accept( this , result ) && result;
        }

        public bool Visit( NotNode node , bool result )
        {
            return !node.SubNodes.First().Accept( this , result );
        }

        public bool Visit( GroupNode node , bool result )
        {
            return node.SubNodes.Aggregate( result , ( res , child ) => child.Accept( this , res ) );
        }

        public bool Visit( IdentifierNode node , bool result )
        {
            return this.Rules.Contains( node.Identifier );
        }
    }

    #region Node Types

    public abstract class NodeBase
    {
        public List<NodeBase> SubNodes = new List<NodeBase>();

        public abstract T Accept<T>( IVisitor<T> visitor , bool result );
    }

    public class OrNode : NodeBase
    {
        public override T Accept<T>( IVisitor<T> visitor , bool result )
        {
            return visitor.Visit( this , result );
        }
    }

    public class NotNode : NodeBase
    {
        public override T Accept<T>( IVisitor<T> visitor , bool result )
        {
            return visitor.Visit( this , result );
        }
    }

    public class AndNode : NodeBase
    {
        public override T Accept<T>( IVisitor<T> visitor , bool result )
        {
            return visitor.Visit( this , result );
        }
    }

    public class GroupNode : NodeBase
    {
        public override T Accept<T>( IVisitor<T> visitor , bool result )
        {
            return visitor.Visit( this , result );
        }
    }

    public class IdentifierNode : NodeBase
    {
        public int Identifier;

        public override T Accept<T>( IVisitor<T> visitor , bool result )
        {
            return visitor.Visit( this , result );
        }
    }

    #endregion

    public class Processor
    {
        public enum OperationTypes
        {
            Not,
            And,
            Or,
        }

        public static Dictionary<string , OperationTypes> OperationTypeMap = new Dictionary<string , OperationTypes> {
            { "NOT" , OperationTypes.Not } ,
            { "AND" , OperationTypes.And } ,
            { "OR"  , OperationTypes.Or  } ,
        };

        public GroupNode Parse( string ruleString )
        {
            var index = 0;
            var rootNode = new GroupNode();

            rootNode.SubNodes = this.Parse( ruleString , ref index );

            return rootNode;
        }

        private List<NodeBase> Parse( string ruleString , ref int index )
        {
            var node = default( NodeBase );
            var nodes = new List<NodeBase>();

            for( ; index < ruleString.Length ; index++ )
            {
                var c = ruleString[ index ];

                if( char.IsWhiteSpace( c ) )
                {
                    continue;
                }
                else if( char.IsLetter( c ) )
                {
                    var opType = this.ScanOperationType( ruleString , ref index );

                    if( opType == OperationTypes.And )
                        node = new AndNode();
                    else if( opType == OperationTypes.Or )
                        node = new OrNode();
                    else if( opType == OperationTypes.Not )
                        node = new NotNode();

                    nodes.Add( node );
                }
                else if( c == '(' )
                {
                    if( node == null )
                    {
                        node = new GroupNode();
                        nodes.Add( node );
                    }

                    index++;

                    node.SubNodes.Add( new GroupNode {
                        SubNodes = this.Parse( ruleString , ref index )
                    } );
                }
                else if( c == ')' )
                {
                    index++;

                    break;
                }
                else if( c == '{' )
                {
                    var idNode = new IdentifierNode {
                        Identifier = this.ScanNumber( ruleString , ref index )
                    };

                    if( node == null )
                    {
                        node = idNode;
                        nodes.Add( idNode );
                    }
                    else
                    {
                        node.SubNodes.Add( idNode );
                    }
                }
                else
                {
                    Debug.Fail( "" );
                }
            }

            return nodes;
        }

        private OperationTypes ScanOperationType( string ruleString , ref int index )
        {
            var idx = index;
            var operationType = OperationTypeMap
                .Where( x => ruleString.Length > x.Key.Length + idx - 1 )
                .Where( x => ruleString.Substring( idx , x.Key.Length ) == x.Key )
                .Single();

            index += operationType.Key.Length;

            return operationType.Value;
        }

        private int ScanNumber( string ruleString , ref int index )
        {
            var sb = new StringBuilder();

            for( var c = ruleString[ ++index ] // skip opening brace
               ; index < ruleString.Length
               ; c = ruleString[ ++index ]
               )
            {
                if( char.IsNumber( c ) )
                    sb.Append( c );
                else if( c == '}' )
                    break;
                else
                    Debug.Fail( "" );
            }

            return Convert.ToInt32( sb.ToString() );
        }
    }
}

1 个答案:

答案 0 :(得分:3)

您可以想象,您的所有实体(NOTORANDID)都来源于Node之类的共同抽象,执行一些基本操作,例如枚举子节点等。然后,如果您有相对较小且固定的节点集,则可以申请类模式Visitor的层次结构。因此,您的代码可能如下所示:

public interface Visitor<T>
{
    T Visit(OrNode node);
    T Visit(NotNode node);
    T Visit(AndNode node);
    T Visit(IdNode node);
}
public abstract class ANode
{
    public List<ANode> Childs { get; }
    public abstract T Accept<T>(Visitor<T> visitor);
}
public class OrNode : ANode
{
    public override T Accept<T>(Visitor<T> visitor)
    {
        return visitor.Visit(this);
    }
}
public class NotNode : ANode
{
    public override T Accept<T>(Visitor<T> visitor)
    {
        return visitor.Visit(this);
    }
}

public class AndNode : ANode
{
    public override T Accept<T>(Visitor<T> visitor)
    {
        return visitor.Visit(this);
    }
}

public class IdNode : ANode
{
    public bool Value;
    public override T Accept<T>(Visitor<T> visitor)
    {
        return visitor.Visit(this);
    }
}

您可以使用下一个访问者计算此表达式的任何子树的值:

public class ValueVisitor : Visitor<bool> {
        public bool Visit(OrNode node) {
            return node.Childs.Aggregate(false, (current, child) => current | child.Accept(this));
        }

        public bool Visit(NotNode node) {
            return !node.Childs[0].Accept(this);
        }

        public bool Visit(AndNode node) {
            return node.Childs.Aggregate(true, (current, child) => current & child.Accept(this));
        }

        public bool Visit(IdNode node) {
            return node.Value;
        }
    }

为了方便地将文本解析为此类结构,您可以在C#中使用Monadic-Parser-Combinators Sprache