TL; DR: Se si desidera visualizzare il codice, passare alla seconda parte della risposta.
Vorrei creare un albero dall'espressione per analizzare e quindi attraversare prima la profondità. È possibile fare riferimento allo wikipedia article about Binary Expression Trees per avere un'idea di ciò che sto suggerendo.
- Inizia aggiungendo le parentesi opzionali omesso di fare il passo successivo più facile
- Quando si legge tutto ciò che non è un operatore o un Parenthese, creare un nodo di tipo LEAF
- Quando si leggono qualsiasi operatore (in il tuo caso
not
, and
, or
), crea il nodo operatore corrispondente
- Gli operatori binari ottengono i nodi precedenti e seguenti come figli, gli operatori unari ottengono solo il successivo.
Quindi, per il tuo esempio ¬((A ∧ B) ∨ C ∨ D)
, l'algoritmo sarebbe andato come questo:
¬((A ∧ B) ∨ C ∨ D)
diventa ¬(((A ∧ B) ∨ C) ∨ D)
- creare un nodo
NOT
, otterrà il risultato delle seguenti parentesi di apertura come un bambino.
- Creare
A
LEAF
nodo, AND
nodo e B
nodo LEAF
. AND
ha A
e B
da bambini.
- Creare il nodo
OR
, ha il AND
creato in precedenza come un bambino e un nuovo nodo LEAF
per C
.
- Creare il nodo
OR
, ha il OR
creato in precedenza e un nuovo nodo per D
come bambini.
A quel punto, il vostro albero assomiglia a questo:
NOT
|
OR
/\
OR D
/\
AND C
/\
A B
È quindi possibile aggiungere un metodo Node.Evaluate() che valuta in modo ricorsivo in base al tipo (polimorfismo potrebbe essere usato qui). Ad esempio, potrebbe essere qualcosa del genere:
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();
}
}
E così via e così via. Per ottenere il risultato della vostra espressione, è allora solo bisogno di chiamare
bool result = Root.Evaluate();
Va bene, dato che non è un incarico ed è in realtà una cosa divertente da realizzare, sono andato avanti. Parte del codice che pubblicherò qui non è correlato a ciò che ho descritto prima (e alcune parti sono mancanti), ma lascerò la parte superiore nella mia risposta come riferimento (nulla di ciò che c'è di sbagliato è sbagliato (si spera!)).
Tenere presente che questo è tutt'altro che ottimale e che mi sono sforzato di non modificare la classe BoolExpr fornita. La modifica potrebbe consentire di ridurre la quantità di codice. Non c'è nemmeno il controllo degli errori.
Ecco il metodo principale
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();
}
La fase di tokenizzazione crea un oggetto token per tutti i segni di espressione. Aiuta a mantenere l'analisi separata dall'algoritmo attuale. Ecco la classe Token che esegue questa:
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;
}
}
}
A quel punto, nel metodo principale, è possibile vedere trasformo l'elenco di token in Polish Notation ordine. Rende la creazione di un albero molto più facile e io uso un'implementazione modificata del Shunting Yard Algorithm per questo:
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();
}
Dopo questa trasformazione, la nostra lista di token diventa NOT, OR, OR, C, D, AND, A, B
.
A questo punto, siamo pronti per creare l'albero delle espressioni. Le proprietà del Polish Notation ci permettono di camminare solo la lista di token e ricorsivamente creiamo i nodi della struttura (useremo la classe BoolExpr
) come andiamo:
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;
}
Ora siamo d'oro! Abbiamo l'albero delle espressioni che rappresenta l'espressione, quindi chiederemo all'utente i valori booleani effettivi di ciascun operando letterale e valuteremo il nodo radice (che valuterà ricorsivamente il resto dell'albero come necessario).
La mia funzione Eval segue, tenete presente che utilizzerei del polimorfismo per rendere questo pulitore se ho modificato la vostra classe 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();
}
Come previsto, alimentando un espressione di prova ¬((A ∧ B) ∨ C ∨ D)
con valori false, true, false, true
per A, B, C, D
cede rispettivamente il risultato false
.
non so se questo potrebbe funzionare, ma proverei a usare [l'algoritmo Shunting Yard] (http://en.wikipedia.org/wiki/Shunting-yard_algorithm) per convertire l'espressione in notazione polacca, che sarebbe facile da analizzare – GolfWolf
Prova [Notazione polacca inversa] (http://en.wikipedia.org/wiki/Reverse_Polish_notation). Ne esistono già molte implementazioni ed esempi in C# su Internet. –
Se come me, vorrei sfruttare questa opportunità per raccogliere F #, unioni specificamente discriminate: http://msdn.microsoft.com/en-us/library/dd233226.aspx – Tormod