Turn boolean-expression string into the .NET code - c#

I have logic where customer specifies a string and my app tells to the customer if this string presents in the text, something like this:
internal const string GlobalText = "blablabla";
bool PresentInTheText(string searchString)
{
return GlobalText.IndexOf(searchString, StringComparison.OrdinalIgnoreCase) >= 0;
}
Basically if text contains passed string return true otherwise false.
Now I want to make it more complex. Lets say if customer passes a string "foo && bar", and I need to return true if this text contains both "foo" and "bar" substrings, straightforward approach:
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;
It works for expressions like A && B && C. But I want generalize the solution to support all boolean operators.
Let's say: ("foo" && "bar") || "baz". What would be the solution?
I would say take passed string, using regex add to all strings .IndexOf(token, StringComparison.OrdinalIgnoreCase) < >= 0 code, it would be like this:
("foo".IndexOf(token, StringComparison.OrdinalIgnoreCase) < >= 0 &&
"bar".IndexOf(token, StringComparison.OrdinalIgnoreCase) < >= 0)) ||
"baz".IndexOf(token, StringComparison.OrdinalIgnoreCase) < >= 0
and then turn this string into a function and execute using Reflections. What would be the best solution?
ETA:
Test cases:
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

You can solve this universally in the same way that a calculator, or a compiler, evaluates an expression:
Tokenize the string and identify each token as an operator (OP) or an operand (A, B, C, etc).
Convert the token sequence from infix (A OP B) to postfix (A B OP).
Evaluate the postfix token sequence.
Each of these steps can be done with a well known stack based algorithm, in linear time and space. Plus, if you use this method, it automatically extends to any binary operators you'd like to add later (addition, subtraction, fuzzy string match, etc etc).
To convert from infix to postfix: http://scriptasylum.com/tutorials/infix_postfix/algorithms/infix-postfix/
To evaluate the postfix:
http://scriptasylum.com/tutorials/infix_postfix/algorithms/postfix-evaluation/

The easiest way to do this would be to parse the input text and build an array of boolean "true" values, so you end up with something like this:
//Dictionary<string,List<string>> members;
members["Car"].Contains("BMW") // evals to True;
Alternatively, if there's no functional difference between any of the input entries (i.e. the variable evaluates to true as long as the word shows up in the input text), you can probably just build a list of strings rather than having to worry about using their classification as the dictionary key.
Then, you parse the equation strings and see if the values are present in the boolean list, if they are, you replace them in the original equation string with a 1. If they are not present, you replace them with a 0.
You end up with something that looks like this:
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"
Now, it's just a simple iterative string replace. You loop on the string until the only thing remaining is a 1 or a 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
}
Alternatively, if you can find a C# "eval" method, you can evaluate the expression directly (and you can also use True/False instead of 0/1).
Edit:
Found a simple tokenizer that will probably work for parsing the test equations:
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
Edit 2:
Just wrote a sample project that does it.
//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());
}
}
This tokenizes the equation and replaces with 1 and 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";
}
}
At this point, the equation "(Danny || Jennifer) && (!BMW)" looks like "(1||0)&&(!1)".
Now reduce the equation to a 1 or 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");
}
Now you should have a string that contains only a 1 or a 0 indicating true or false, respectively.

With the help of DynamicExpresso you can easily do this in 10 lines. Let's say the text and the user input are like this:
var text = "Bob and Tom are in the same class.";
var input = "(Bob || Alice) && Tom";
You can consider "Bob" "Alice" "Tom" are variables whose type is bool in C#, the user input string becomes a valid C# expression, evaulate it using DynamicExpresso and get a bool result.
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();

Related

How to test if a string array is boolean

I need to test for all values being only "0" or "1" in a string array and tried the following code without success:
bool isBool = Array.TrueForAll(str, val => val.Trim() == "0" || val.Trim() == "1");
and
bool isBool = str.All(val => val.Trim() == "0" || val.Trim() == "1");
Is there a different way I have to do this because I'm using a || condition?
I'm assuming that you mean the values have to be exclusively all 1's or 0's? In that case you need to test each case separately. Your existing code is true when the array contains only 0's or 1's. It would be false if any of the values were 2 though. One solution:
var allAreZero = new string[] { " 0 ", " 0", " 0" };
var allAreOne = new string[] { " 1 ", "1", " 1" };
var mixedOnesAndZeros = new string[] { " 1 ", "0", " 1" };
private bool ArrayIsExclusivelyTrueOrFalse(string[] data)
{
return data.All(i => int.TryParse(i, out int value) && value == 0) ||
data.All(i => int.TryParse(i, out int value) && value == 1);
}
ArrayIsExclusivelyTrueOrFalse(allAreZero); // true
ArrayIsExclusivelyTrueOrFalse(allAreOne); // true
ArrayIsExclusivelyTrueOrFalse(mixedOnesAndZeros); // false
Try this
var isBool = !str.Select(s => s.Trim()).Distinct().Any(s => s != "0" && s != "1");
you can try Any() and return in the first failing condition !str.Any(x => x.Trim() != "0" && x.Trim() != "1")
If you want to ensure that all the values in your array are either ALL "0" or ALL "1" (but not a mix of "0" and "1", as I originally read it), then one way would be to ensure that the number of Distinct items (after trimming) has a Count of 1 (meaning they're all the same), and that the item is a member of the set of valid values (in the code below we just check the first item, since we already know they're all the same at that point).
For example:
var validValues = new[] {"0", "1"};
var isBool = str.Select(i => i.Trim()).Distinct().Count() == 1 &&
validValues.Contains(str[0].Trim());

Get the substring of the non conditional part

I have this string for example:
2X+4+(2+2X+4X) +4
The position of the parenthesis can vary. I want to find out how can I extract the part without the parenthesis. For example I want 2X+4+4. Any Suggestions?
I am using C#.
Try simple string Index and Substring operations as follows:
string s = "2X+4+(2+2X+4X)+4";
int beginIndex = s.IndexOf("(");
int endIndex = s.IndexOf(")");
string firstPart = s.Substring(0,beginIndex-1);
string secondPart = s.Substring(endIndex+1,s.Length-endIndex-1);
var result = firstPart + secondPart;
Explanation:
Get the first index of (
Get the first index of )
Create two sub-string, first one is 1 index before beginIndex to remove the mathematical symbol like +
Second one is post endIndex, till string length
Concatenate the two string top get the final result
Try Regex approach:
var str = "(1x+2)-2X+4+(2+2X+4X)+4+(3X+3)";
var regex = new Regex(#"\(\S+?\)\W?");//matches '(1x+2)-', '(2+2X+4X)+', '(3X+3)'
var result = regex.Replace(str, "");//replaces parts above by blank strings: '2X+4+4+'
result = new Regex(#"\W$").Replace(result, "");//replaces last operation '2X+4+4+', if needed
//2X+4+4 ^
Try this one:
var str = "(7X+2)+2X+4+(2+2X+(3X+3)+4X)+4+(3X+3)";
var result =
str
.Aggregate(
new { Result = "", depth = 0 },
(a, x) =>
new
{
Result = a.depth == 0 && x != '(' ? a.Result + x : a.Result,
depth = a.depth + (x == '(' ? 1 : (x == ')' ? -1 : 0))
})
.Result
.Trim('+')
.Replace("++", "+");
//result == "2X+4+4"
This handles nested, preceding, and trailing parenthesis.

How to check a String for characters NOT to be included in C#

So I have a String like:
String myString = "AAAaAAA";
I want to check the String if it contains ANY characters that are not "A"
How can I do this? my previous code is:
Regex myChecker = new Regex("[^A.$]$");
if (checkForIncluded.IsMatch(myString))
{
//Do some Stuff
}
Is there any other way to do it? The code above does not detect the small a. But when I use a different String with only characters that are not "A" it works. Thank you!
String myString = "AAAaAAA";
if(myString.Any(x => x != 'A')) {
// Yep, contains some non-'A' character
}
Try something like this:
var allowedChars = new List<char>() { 'a', 'b', 'c' };
var myString = "abcA";
var result = myString.Any(c => !allowedChars.Contains(c));
if (result) {
// myString contains something not in allowed chars
}
or even like this:
if (myString.Except(allowedChars).Any()) {
// ...
}
allowedChars can be any IEnumerable< char >.
I want to check the String if it contains ANY characters that are not
"A"
You can use Enumerable.Any like;
string myString = "AAAaAAA";
bool b = myString.Any(s => !s.Equals('A')); // True
You can use Linq:
String myString = "AAAaAAA";
var result = myString.Where(x=>x != 'A'); // return all character that are not A
if(result.Count() > 0)
{
Console.WriteLine("Characters exists other than a");
}
if you want both cases:
String myString = "AAAaAAA";
var result = myString.Where(x=>x != 'A' || x != 'a');
or Use String.Equals():
var result = myString.Where(x => !String.Equals(x.ToString(), "A", StringComparison.OrdinalIgnoreCase));
Your regular expression is only trying to match the last character. This should work:
var myString = "AAaA";
bool anyNotAs = Regex.IsMatch(myString, "[^A]", RegexOptions.None);

how to get specific string from list box

i have a List box with some texts , i wanna get all the strings after Other:
and this is the code i use
string[] causearr = lst_Box.Items[i].Value.Split('-');
SqlSrcComplainCgeAnalysis.InsertParameters["ID"].DefaultValue =causearr[0].ToString(); SqlSrcComplainCgeAnalysis.InsertParameters["subId"].DefaultValue = causearr[2].ToString();
if (txt_OtherCause.Text != string.Empty && txt_OtherCause.Visible == true && (int.Parse(causearr[2].ToString()) == 7 || int.Parse(causearr[2].ToString()) == 15 || int.Parse(causearr[2].ToString()) == 21))
SqlSrcComplainCgeAnalysis.InsertParameters["CauseComments"].DefaultValue = causearr[3].ToString().Substring(causearr[3].ToString().LastIndexOf(":") + 3);
else
SqlSrcComplainCgeAnalysis.InsertParameters["CauseComments"].DefaultValue = string.Empty;
and this is the data in the List Box
Cause:SpareParts-SubCause:Others: First Line
Cause:Technical-SubCause:Others: Second Line
so how to return the data after "Others:" if this data changes , and also the Cause changes according to the user selection .
string s = "Cause:SpareParts-SubCause:Others: First Line".Split(new char[] { ' ' }, 2)[1];
string value = "Cause:SpareParts-SubCause:Others: First Line"
string[] lines = Regex.Split(value, ":Others:");
you now need lines[1] i guess
What you can do is split the string first on the colons ':', and then the last item in the array would be the data that you want.
result[0] == 'Cause'
result[1] == 'SpareParts-SubCause'
result[2] == 'Others'
result[4] == 'Cause'
result[5] == 'First Line'
or you could use a Regex to do all this in a cool way:
http://msdn.microsoft.com/en-us/library/hs600312.aspx

using too many booleans

Is there a better way to do this?
foreach (var line in lines)
{
bool t01 = line.Model.ToLower() == model;
bool t02 = line.Authority.ToLower() != "unknown";
bool t101 = line.Type.ToLower() == "adcn";
bool t102 = line.Type.ToLower() == "adcn/adv";
bool t103 = line.Type.ToLower() == "bn";
bool t104 = line.Type.ToLower() == "book";
bool t105 = line.Type.ToLower() == "cancel";
bool t106 = line.Type.ToLower() == "cir";
bool t107 = line.Type.ToLower() == "coord sht";
bool t108 = line.Type.ToLower() == "cre";
bool t109 = line.Type.ToLower() == "ddr";
bool t110 = line.Type.ToLower() == "dl";
if (t01 && t02)
if ((t101 || t102 || t103 || t104 || t105 || t106 || t107 || t108 || t109 || t110))
Console.WriteLine(line);
}
It actually goes up to t139. Clipped it for brevity.
It sounds like you need a HashSet<string> for the types:
static readonly HashSet<string> ValidTypes = new HashSet<string>
(StringComparer.OrdinalIgnoreCase)
{
"adcn", "adcn/adv", "bn" ...
};
if (line.Model.Equals(model, StringComparison.OrdinalIgnoreCase) &&
!line.Authority.Equals("unknown", StringComparison.OrdinalIgnoreCase) &&
validTypes.Contains(line.Type))
{
Console.WriteLine(line);
}
That will also be faster than comparing the string for each item individually. Note that although I've used OrdinalIgnoreCase in the above, that may not be what you really want - you may want CurrentCultureIgnoreCase or InvariantCultureIgnoreCase.
(Note that lower-casing strings in order to perform a case-insensitive comparison is a bad idea - particularly if you're just using the default locale to do it in. For example, if you lower-case "MAIL" and your current locale is Turkish, you won't get "mail".)
string[] validTypes = new string[] { "adcn", "adcn/adv", "bn", "book" /*, ...*/ };
foreach (var line in lines)
{
bool t01 = line.Model.ToLower() == model;
bool t02 = line.Authority.ToLower() != "unknown";
if(t01 && t02 && validTypes.Contains(line.Type.ToLower())
Console.WriteLine(line);
}
What do you mean by better? Just off the top of my head, you are evaluating 139 conditions, and then writing the line to the console if any of them are true. It would be more efficient to short circuit if the first one (or the third one, or the fourth, etc) was true and not bother evaluating the rest.
You can do this by storing the evaluation functions in a list:
var cases = new List<Func<Line>>();
cases.Add(l => l.Model.ToLower() == model); //Be careful of the closure here
cases.Add(l => l.Authority.ToLower() != "unknown");
... etc ...
and then evaluating the functions in order for each line, exiting early if the current function returns true:
if (cases.Any(c=>c(line)))
Console.WriteLine(line);
You could use a BitArray
or just plain LINQ
var types = new List<string>{ "adcn", "adcn/adv" }; // etc
if (types.Any(t => t == line.Type.ToLower()))
{
Console.WriteLine(line);
}
Considering your example:
var types = new List<string>{ "adcn", "adcn/adv" }; // etc
foreach (var line in lines)
{
if (types.Any(t => t == line.Type.ToLower()))
{
Console.WriteLine(line);
}
}
Or maybe
var types = new List<string>{ "adcn", "adcn/adv" }; // etc
foreach (var line in lines.Where(line => types.Any(t => t == line.Type.ToLower())))
{
Console.WriteLine(line);
}

Categories

Resources