将boolean-expression字符串转换为.NET代码

时间:2016-12-01 22:49:56

标签: c# .net string boolean-logic boolean-expression

我有一个逻辑,客户指定一个字符串,我的应用程序告诉客户该字符串是否出现在文本中,如下所示:

internal const string GlobalText = "blablabla";

bool PresentInTheText(string searchString)
{
  return GlobalText.IndexOf(searchString, StringComparison.OrdinalIgnoreCase) >= 0;
}

基本上,如果text包含传递的字符串,则返回true,否则返回false。

现在我想让它更复杂。假设客户传递字符串"foo && bar",如果此文本同时包含"foo""bar"子字符串,我需要返回true,直截了当的方法:

bool result;
if (!string.IsNullOrEmpty(passedExpression) && 
passedExpression.Contains(" && "))
{
    var tokens = passedExpression.Split(new[] { " && " }, StringSplitOptions.RemoveEmptyEntries);
    result = true;
    foreach (var token in tokens)
    {
        if (GlobalText.IndexOf(token, StringComparison.OrdinalIgnoreCase) < 0)
        {
            result = false;
        }
    }
}
return result;

适用于像A && B && C这样的表达式。但我想概括解决方案以支持所有布尔运算符。 让我们说:("foo" && "bar") || "baz"。会有什么解决方案?

我会说采用传递的字符串,使用正则表达式添加到所有字符串.IndexOf(token, StringComparison.OrdinalIgnoreCase) < >= 0代码,它将是这样的:

("foo".IndexOf(token, StringComparison.OrdinalIgnoreCase) < >= 0 && 
"bar".IndexOf(token, StringComparison.OrdinalIgnoreCase) < >= 0)) ||
"baz".IndexOf(token, StringComparison.OrdinalIgnoreCase) < >= 0

然后将此字符串转换为函数并使用Reflections执行。什么是最好的解决方案?

ETA

测试用例:

bool Contains(string text, string expressionString);

string text = "Customers: David, Danny, Mike, Luke. Car: BMW"

string str0 = "Luke"
string str1 = "(Danny || Jennifer) && (BMW)"
string str2 = "(Mike && BMW) || Volvo"
string str3 = "(Mike || David) && Ford"
string str4 = "David && !BMW"

bool Contains(string text, string str0);  //True - This text contains "Luke"
bool Contains(string text, string str1);  //True - David and BMW in the text
bool Contains(string text, string str2);  //True - Mike and BMW in the text
bool Contains(string text, string str3);  //False - no Ford in the list
bool Contains(string text, string str4);  //False - BMW in the list

3 个答案:

答案 0 :(得分:2)

您可以像计算器或编译器评估表达式一样普遍解决这个问题:

  1. 对字符串进行标记,并将每个标记标识为运算符(OP)或操作数(A,B,C等)。
  2. 将令牌序列从中缀(A OP B)转换为后缀(A B OP)。
  3. 评估后缀令牌序列。
  4. 这些步骤中的每一步都可以在线性时间和空间中使用众所周知的基于堆栈的算法来完成。另外,如果您使用此方法,它会自动扩展到您以后要添加的任何二元运算符(加法,减法,模糊字符串匹配等)。

    要从中缀转换为后缀:http://scriptasylum.com/tutorials/infix_postfix/algorithms/infix-postfix/

    评估后缀: http://scriptasylum.com/tutorials/infix_postfix/algorithms/postfix-evaluation/

答案 1 :(得分:1)

最简单的方法是解析输入文本并构建一个布尔“true”值的数组,所以最终会得到这样的结果:

//Dictionary<string,List<string>> members;
members["Car"].Contains("BMW") // evals to True;

或者,如果任何输入条目之间没有功能差异(即只要单词出现在输入文本中,变量的计算结果为true),您可能只需构建一个字符串列表而不必担心关于使用他们的分类作为字典键。

然后,你解析方程字符串,看看这些值是否存在于布尔列表中,如果是,则用原始方程字符串中的1替换它们。如果它们不存在,则将它们替换为0

你最终会看到这样的东西:

string str0 = "Luke" // "1"
string str1 = "(Danny || Jennifer) && (BMW)" // "(1 || 0) && (1)"
string str2 = "(Mike && BMW) || Volvo" // "(1 && 1) || 0"
string str3 = "(Mike || David) && Ford" // "(1 || 1) && 0"
string str4 = "David && !BMW" // "1 && !0"

现在,它只是一个简单的迭代字符串替换。你在字符串上循环,直到唯一剩下的是1或0。

while (str.Length > 1)
{
  if (str.Contains("(1 || 1)"))
    str.Replace("(1 || 1)", "1");
  if (str.Contains("(1 || 0)"))
    str.Replace("(1 || 0)", "1");
  // and so on
}

或者,如果您可以找到C#“eval”方法,则可以直接评估表达式(也可以使用True / False而不是0/1)。

编辑:

找到一个简单的标记器,它可能用于解析测试方程式:

using System;
using System.Text.RegularExpressions;

public static string[] Tokenize(string equation)
{
    Regex RE = new Regex(@"([\(\)\! ])");
    return (RE.Split(equation));
}
//from here: https://www.safaribooksonline.com/library/view/c-cookbook/0596003390/ch08s07.html

编辑2: 刚刚写了一个样本项目。

//this parses out the string input, does not use the classifications
List<string> members = new List<string>();
string input = "Customers: David, Danny, Mike, Luke. Car: BMW";
string[] t1 = input.Split(new string[] {". "},         StringSplitOptions.RemoveEmptyEntries);
foreach (String t in t1)
{
  string[] t2 = t.Split(new string[] { ": " }, StringSplitOptions.RemoveEmptyEntries);
  string[] t3 = t2[1].Split(new string[] { "," }, StringSplitOptions.RemoveEmptyEntries);
  foreach (String s in t3)
  {
    members.Add(s.Trim());
  }
}

这会对等式进行标记,并替换为1和0.

string eq = "(Danny || Jennifer) && (!BMW)";
Regex RE = new Regex(@"([\(\)\! ])");
string[] tokens = RE.Split(eq);
string eqOutput = String.Empty;
string[] operators = new string[] { "&&", "||", "!", ")", "("};
foreach (string tok in tokens)
{
  if (tok.Trim() == String.Empty)
    continue;
  if (operators.Contains(tok))
  {
    eqOutput += tok;
  }
  else if (members.Contains(tok))
  {
    eqOutput += "1";
  }
  else 
  {
    eqOutput += "0";
  }
}

此时,方程式“(Danny || Jennifer)&amp;&amp;(!BMW)”看起来像“(1 || 0)&amp;&amp;(!1)”。

现在将等式减少到1或0。

while (eqOutput.Length > 1)
{
  if (eqOutput.Contains("!1"))
    eqOutput = eqOutput.Replace("!1", "0");
  else if (eqOutput.Contains("!0"))
    eqOutput = eqOutput.Replace("!0", "1");
  else if (eqOutput.Contains("1&&1"))
    eqOutput = eqOutput.Replace("1&&1", "1");
  else if (eqOutput.Contains("1&&0"))
    eqOutput = eqOutput.Replace("1&&0", "0");
  else if (eqOutput.Contains("0&&1"))
    eqOutput = eqOutput.Replace("0&&1", "0");
  else if (eqOutput.Contains("0&&0"))
    eqOutput = eqOutput.Replace("0&&0", "0");
  else if (eqOutput.Contains("1||1"))
    eqOutput = eqOutput.Replace("1||1", "1");
  else if (eqOutput.Contains("1||0"))
    eqOutput = eqOutput.Replace("1||0", "1");
  else if (eqOutput.Contains("0||1"))
    eqOutput = eqOutput.Replace("0||1", "1");
  else if (eqOutput.Contains("0||0"))
    eqOutput = eqOutput.Replace("0||0", "0");
  else if (eqOutput.Contains("(1)"))
    eqOutput = eqOutput.Replace("(1)", "1");
  else if (eqOutput.Contains("(0)"))
    eqOutput = eqOutput.Replace("(0)", "0");
}

现在你应该有一个只包含1或0的字符串,分别表示真或假。

答案 2 :(得分:0)

DynamicExpresso的帮助下,您可以轻松地在10行中执行此操作。让我们说文本和用户输入是这样的:

var text = "Bob and Tom are in the same class.";
var input = "(Bob || Alice) && Tom";

你可以考虑&#34; Bob&#34; &#34;爱丽丝&#34; &#34;汤姆&#34;是变量,其类型为C#中的 bool ,用户输入字符串成为有效的C#表达式,使用DynamicExpresso进行评估并获得 bool 结果。

var variables = input.Split(new[] { "(", "||", "&&", ")", " " }, 
    StringSplitOptions.RemoveEmptyEntries);

var interpreter = new Interpreter();

foreach (var variable in variables)
{
    interpreter.SetVariable(variable, text.Contains(variable));
}

var result = (bool)interpreter.Parse(input).Invoke();