14

私は次のBoolExprクラスを持っています:

class BoolExpr
{
    public enum BOP { LEAF, AND, OR, NOT };

    //
    //  inner state
    //

    private BOP    _op;
    private BoolExpr _left;
    private BoolExpr _right;
    private String   _lit;

    //
    //  private constructor
    //

    private BoolExpr(BOP op, BoolExpr left, BoolExpr right)
    {
        _op = op;
        _left  = left;
        _right = right;
        _lit = null;
    }

    private BoolExpr(String literal)
    {
        _op = BOP.LEAF;
        _left  = null;
        _right = null;
        _lit = literal;
    }

    //
    //  accessor
    //

    public BOP Op
    {
        get { return _op;  }
        set { _op = value; }
    }

    public BoolExpr Left
    {
        get { return _left;  }
        set { _left = value; }
    }

    public BoolExpr Right
    {
        get { return _right;  }
        set { _right = value; }
    }

    public String Lit
    {
        get { return _lit; }
        set { _lit = value; }
    }

    //
    //  public factory
    //

    public static BoolExpr CreateAnd(BoolExpr left, BoolExpr right)
    {
        return new BoolExpr(BOP.AND, left, right);
    }

    public static BoolExpr CreateNot(BoolExpr child)
    {
        return new BoolExpr(BOP.NOT, child, null);
    }

    public static BoolExpr CreateOr(BoolExpr left, BoolExpr right)
    {
        return new BoolExpr(BOP.OR, left, right);
    }

    public static BoolExpr CreateBoolVar(String str)
    {
        return new BoolExpr(str);
    }

    public BoolExpr(BoolExpr other)
    {
        // No share any object on purpose
        _op = other._op;
        _left  = other._left  == null ? null : new BoolExpr(other._left);
        _right = other._right == null ? null : new BoolExpr(other._right);
        _lit = new StringBuilder(other._lit).ToString();
    }

    //
    //  state checker
    //

    Boolean IsLeaf()
    {
        return (_op == BOP.LEAF);
    }

    Boolean IsAtomic()
    {
        return (IsLeaf() || (_op == BOP.NOT && _left.IsLeaf()));
    }
}

¬((A ∧ B) ∨ C ∨ D)" " のような入力ブール式文字列を解析して上記のクラスにロードするには、どのアルゴリズムを使用すればよいですか?

4

2 に答える 2

51

TL;DR: コードを見たい場合は、回答の 2 番目の部分にジャンプしてください。

式からツリーを構築して解析し、最初に深さをトラバースします。Binary Expression Trees に関するウィキペディアの記事を参照して、私が提案していることの感触をつかむことができます。

  1. 次のステップを簡単にするために、省略されたオプションの括弧を追加することから始めます
  2. 演算子または括弧以外のものを読み取る場合は、LEAF タイプのノードを作成します
  3. 任意の演算子 (あなたの場合notは , and, or) を読み取るときは、対応する演算子ノードを作成します
  4. 二項演算子は前後のノードを子として取得し、単項演算子は次のノードのみを取得します。

したがって、あなたの例¬((A ∧ B) ∨ C ∨ D)では、アルゴリズムは次のようになります。

¬((A ∧ B) ∨ C ∨ D)になる¬(((A ∧ B) ∨ C) ∨ D)

  1. ノードを作成するNOTと、次の開き括弧の結果が子として取得されます。
  2. A LEAFノード、ANDノード、ノードを作成しB LEAFます。 と子供としてAND持っています。AB
  3. ノードを作成ORします。以前ANDに子として作成された と の新しいLEAFノードがありCます。
  4. ノードを作成ORします。以前に作成ORしたノードと、子としての新しいノードがありますD

その時点で、ツリーは次のようになります。

  NOT
   |
  OR
  /\
 OR D
 / \
AND C
/\
A B

次に、そのタイプに基づいて再帰的に評価する Node.Evaluate() メソッドを追加できます (ここではポリモーフィズムを使用できます)。たとえば、次のようになります。

class LeafEx {
    bool Evaluate() {
        return Boolean.Parse(this.Lit);
    }
}

class NotEx {
    bool Evaluate() {
        return !Left.Evaluate();
    }
}

class OrEx {
    bool Evaluate() {
        return Left.Evaluate() || Right.Evaluate();
    }
}

などなど。式の結果を取得するには、呼び出すだけです

bool result = Root.Evaluate();

これは課題ではなく、実際に実装するのが楽しいものなので、先に進みました。ここに投稿するコードの一部は、以前に説明したものとは関係ありません (一部が欠落しています) が、参照用に答えの一番上の部分を残します (何も間違っていないことを願っています)。

これは最適とはほど遠いものであり、提供された BoolExpr クラスを変更しないように努力したことを覚えておいてください。これを変更すると、コードの量を減らすことができます。また、エラーチェックもまったくありません。

主な方法はこちら

static void Main(string[] args)
{
    //We'll use ! for not, & for and, | for or and remove whitespace
    string expr = @"!((A&B)|C|D)";
    List<Token> tokens = new List<Token>();
    StringReader reader = new StringReader(expr);

    //Tokenize the expression
    Token t = null;
    do
    {
        t = new Token(reader);
        tokens.Add(t);
    } while (t.type != Token.TokenType.EXPR_END);

    //Use a minimal version of the Shunting Yard algorithm to transform the token list to polish notation
    List<Token> polishNotation = TransformToPolishNotation(tokens);

    var enumerator = polishNotation.GetEnumerator();
    enumerator.MoveNext();
    BoolExpr root = Make(ref enumerator);

    //Request boolean values for all literal operands
    foreach (Token tok in polishNotation.Where(token => token.type == Token.TokenType.LITERAL))
    {
        Console.Write("Enter boolean value for {0}: ", tok.value);
        string line = Console.ReadLine();
        booleanValues[tok.value] = Boolean.Parse(line);
        Console.WriteLine();
    }

    //Eval the expression tree
    Console.WriteLine("Eval: {0}", Eval(root));

    Console.ReadLine();
}

トークン化フェーズでは、式のすべてのトークンに対して Token オブジェクトが作成されます。解析を実際のアルゴリズムから分離しておくのに役立ちます。これを実行する Token クラスは次のとおりです。

class Token
{
    static Dictionary<char, KeyValuePair<TokenType, string>> dict = new Dictionary<char, KeyValuePair<TokenType, string>>()
    {
        {
            '(', new KeyValuePair<TokenType, string>(TokenType.OPEN_PAREN, "(")
        },
        {
            ')', new KeyValuePair<TokenType, string>(TokenType.CLOSE_PAREN, ")")
        },
        {
            '!', new KeyValuePair<TokenType, string>(TokenType.UNARY_OP, "NOT")
        },
        {
            '&', new KeyValuePair<TokenType, string>(TokenType.BINARY_OP, "AND")
        },
        {
            '|', new KeyValuePair<TokenType, string>(TokenType.BINARY_OP, "OR")
        }
    };

    public enum TokenType
    {
        OPEN_PAREN,
        CLOSE_PAREN,
        UNARY_OP,
        BINARY_OP,
        LITERAL,
        EXPR_END
    }

    public TokenType type;
    public string value;

    public Token(StringReader s)
    {
        int c = s.Read();
        if (c == -1)
        {
            type = TokenType.EXPR_END;
            value = "";
            return;
        }

        char ch = (char)c;

        if (dict.ContainsKey(ch))
        {
            type = dict[ch].Key;
            value = dict[ch].Value;
        }
        else
        {
            string str = "";
            str += ch;
            while (s.Peek() != -1 && !dict.ContainsKey((char)s.Peek()))
            {
                str += (char)s.Read();
            }
            type = TokenType.LITERAL;
            value = str;
        }
    }
}

その時点で、main メソッドで、トークンのリストをポーランド記法順に変換していることがわかります。これにより、ツリーの作成がはるかに簡単になります。これには、Shunting Yard Algorithmの修正された実装を使用します。

static List<Token> TransformToPolishNotation(List<Token> infixTokenList)
{
    Queue<Token> outputQueue = new Queue<Token>();
    Stack<Token> stack = new Stack<Token>();

    int index = 0;
    while (infixTokenList.Count > index)
    {
        Token t = infixTokenList[index];

        switch (t.type)
        {
            case Token.TokenType.LITERAL:
                outputQueue.Enqueue(t);
                break;
            case Token.TokenType.BINARY_OP:
            case Token.TokenType.UNARY_OP:
            case Token.TokenType.OPEN_PAREN:
                stack.Push(t);
                break;
            case Token.TokenType.CLOSE_PAREN:
                while (stack.Peek().type != Token.TokenType.OPEN_PAREN)
                {
                    outputQueue.Enqueue(stack.Pop());
                }
                stack.Pop();
                if (stack.Count > 0 && stack.Peek().type == Token.TokenType.UNARY_OP)
                {
                    outputQueue.Enqueue(stack.Pop());
                }
                break;
            default:
                break;
        }

        ++index;
    }
    while (stack.Count > 0)
    {
        outputQueue.Enqueue(stack.Pop());
    }

    return outputQueue.Reverse().ToList();
}

この変換の後、トークン リストは になりNOT, OR, OR, C, D, AND, A, Bます。

この時点で、式ツリーを作成する準備が整いました。ポーランド記法のプロパティにより、トークン リストをたどり、再帰的にツリー ノードを作成することができます (BoolExprクラスを使用します)。

static BoolExpr Make(ref List<Token>.Enumerator polishNotationTokensEnumerator)
{
    if (polishNotationTokensEnumerator.Current.type == Token.TokenType.LITERAL)
    {
        BoolExpr lit = BoolExpr.CreateBoolVar(polishNotationTokensEnumerator.Current.value);
        polishNotationTokensEnumerator.MoveNext();
        return lit;
    }
    else
    {
        if (polishNotationTokensEnumerator.Current.value == "NOT")
        {
            polishNotationTokensEnumerator.MoveNext();
            BoolExpr operand = Make(ref polishNotationTokensEnumerator);
            return BoolExpr.CreateNot(operand);
        }
        else if (polishNotationTokensEnumerator.Current.value == "AND")
        {
            polishNotationTokensEnumerator.MoveNext();
            BoolExpr left = Make(ref polishNotationTokensEnumerator);
            BoolExpr right = Make(ref polishNotationTokensEnumerator);
            return BoolExpr.CreateAnd(left, right);
        }
        else if (polishNotationTokensEnumerator.Current.value == "OR")
        {
            polishNotationTokensEnumerator.MoveNext();
            BoolExpr left = Make(ref polishNotationTokensEnumerator);
            BoolExpr right = Make(ref polishNotationTokensEnumerator);
            return BoolExpr.CreateOr(left, right);
        }
    }
    return null;
}

今、私たちはゴールデンです!式を表す式ツリーがあるので、ユーザーに各リテラル オペランドの実際のブール値を尋ね、ルート ノードを評価します (必要に応じてツリーの残りの部分を再帰的に評価します)。

私の Eval 関数は次のとおりBoolExprです。クラスを変更した場合、ポリモーフィズムを使用してこれをよりクリーンにすることに注意してください。

static bool Eval(BoolExpr expr)
{
    if (expr.IsLeaf())
    {
        return booleanValues[expr.Lit];
    }

    if (expr.Op == BoolExpr.BOP.NOT)
    {
        return !Eval(expr.Left);
    }

    if (expr.Op == BoolExpr.BOP.OR)
    {
        return Eval(expr.Left) || Eval(expr.Right);
    }

    if (expr.Op == BoolExpr.BOP.AND)
    {
        return Eval(expr.Left) && Eval(expr.Right);
    }

    throw new ArgumentException();
}

予想通り、テスト式にそれぞれの¬((A ∧ B) ∨ C ∨ D)false, true, false, trueを与えるA, B, C, Dと結果が得られfalseます。

于 2013-07-10T13:55:39.067 に答える