Как динамически оценивать логическую логику в строке в c # без использования выражений? [Дубликат]

с моей стороны, я думаю, что вы можете, но вам придется делать файлы для подключения к базе данных, потому что mysqli_* будет принимать только mysqli_connect, и поэтому с помощью mysql_*

вы можете попробовать при обучении PDO Better

9
задан ToolmakerSteve 10 July 2013 в 11:21
поделиться

2 ответа

TL; DR: Если вы хотите увидеть код, перейдите ко второй части ответа.

Я бы построил дерево из выражения для синтаксического анализа, а затем сначала переместил его по глубине. Вы можете обратиться к статье wikipedia о деревьях двоичных выражений , чтобы понять, что я предлагаю.

  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 имеет A и B как дети.
  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;
        }
    }
}

. В этот момент в основном методе вы можете увидеть, что я преобразую список токенов в польский Notation . Это упрощает создание дерева, и я использую модифицированную реализацию алгоритма 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.

39
ответ дан Todd Menier 22 August 2018 в 01:44
поделиться
  • 1
    «Рекурсивный спускный анализатор» это поможет. Каждый "(" запускает подвыражение (вызывает метод "Выражение"), который заканчивается совпадением ")". Некоторые из ответов в stackoverflow.com/questions/2969561/… могут помочь. – ToolmakerSteve 10 July 2013 в 15:27
  • 2
  • 3
    @Meysam Я обновил свой ответ с гораздо более подробной информацией, так как вы говорите мне, что это не домашнее задание или что-то еще. Все это должно быть самодостаточным рабочим примером. – Anthony Vallée-Dubois 12 July 2013 в 16:48
  • 4
    Это замечательный ответ, спасибо. – Dan 3 December 2013 в 01:32
  • 5
    Спасибо за это. Для обработки операторов сравнения вместо передачи в true / false необходимо обработать операнды справа налево? Я заметил, что 10 > 5 становится > 5 10. – Ben Foster 25 December 2016 в 11:35

С точки зрения алгоритма для анализа выражения вам нужен один стек.

Мы используем алгоритм двух шагов:

  1. Лексинг

Цель лексинга состоит в том, чтобы получить «ключевые слова», «идентификаторы» и «разделители»: - ключевым словом является «if», а затем «else» ('') '' / \ '' / ' и т. д. - Идентификаторы в вашем случае - «A», «B», «C» и т. д. - разделитель - это пустое пространство, табуляция, конец строки, конец файла и т. д. ...

Лексинг состоит из использования автоматов. В лексике вы будете читать свой символ ввода char. Когда вы получаете символ, который совместим с одним из ваших ключевых слов, идентификаторов, разделителей, вы начинаете последовательность символов. Когда вы присоединяете разделители, вы останавливаете последовательность, посмотрите в словарной строке последовательности - это ключевое слово (если оно не является идентификатором); затем поместите кортеж [последовательность, ключевое слово или идентификатор / класс] в стек.

Я оставляю вас как упражнение в случае маленького ключевого слова '(', которое также можно увидеть как разделители.

  1. Разбор

Анализ аналогичен грамматике. В вашем случае единственными правилами для проверки являются запятая, двоичные операции и просто простой идентификатор.

] formy:

expression::
  '(' expression ')'
  expression /\ expression
  expression \/ expression
  identifier

Это может быть запись рекурсивной функцией. Сначала отмените свой стек, а затем:

myParseExpression(stack, myC#ResultObject)
{
    if(stack.top = kewyord.'('  )
         then myParseOpenComma(all stack but top, myC#ResultObject)
    if(stack.top = keyword.'/\')
         then myParseBinaryAnd(stack, myC#ResultObject)
}

myParseOpenComma(stack, myC#ResultObject)
{
 ...
}

myParseBinaryAnd(stack, myC#ResultObject)
{
 myNewRigthPartOfExpr = new C#ResultObject
 myParseExpression(stack.top, myNewRigthPartOfExpr)
 remove top of stack;
 myNewLeftPartOfExpr = new C#ResultObject
 myParseExpression(stack.top, myNewLeftPartOfExpr)

 C#ResultObject.add("AND", myNewRigthPartOfExpr, myNewLeftPartOfExpr)
}

...

Существует несколько функций, которые разделяют рекурсию друг на друга.

  • Лексинг традиционно выполняется лексером (например, lex tool).
  • Парсинг традиционно выполняется парсером (например, инструмент бизона ).
  • Инструмент позволяет записывать функцию thoses больше, чем я сделал в формальном выражении.

Аспект Thoses является основой компиляции программы. Кодирование thoses вещь улучшит вас много, потому что это тяжело и фундаментально.

4
ответ дан Galigator 22 August 2018 в 01:44
поделиться
  • 1
    Я не стану истинным кодом C #, потому что вопрос выглядит как задание / домашнее задание. – Galigator 10 July 2013 в 14:31
Другие вопросы по тегам:

Похожие вопросы: