-1

I don't know if this is the right question for here. Let's say I have a String with Values:

A = "ab1, eb4, gr3, ..."

Then there are two more Strings B containing must-have values and C not-allowed values. Unfortunately B and C can contain logical AND / OR within the strings.

e.g.

B = "(ut2 | eg1) & ab1"

C = "ke2 & pa5"

Now I am looking for a way to check wether A is valid for B and C. True or False.

For A it's pretty easy to convert the String to List of String containing the values seperately and enabling LINQ expressions on that list.

But how can I best "parse" the information in B and C to evaluate it with an LINQ expression on List A ?

Think ideal solution would be to have B and C each as just one logical expression to directly have List A checked on them. But how to start with that?

Thanks for your support and best regards.

fox
  • 25
  • 8
  • It sounds like you are looking for something very similar to how OData does filtering. – TeaBaerd Jun 09 '20 at 10:30
  • Please let me add that it has to end up i a software solution excepting VB.net or C# code but it's not possible to import librarys. So I am looking for a way with framework functionality. – fox Jun 09 '20 at 10:34
  • I'm not sure why you looking towards accomplishing this with LINQ. You should probably just look at making your own function to evaluate this – Anu6is Jun 09 '20 at 11:32
  • This is not a trivial problem; you'd have to somehow parse the logical expressions. Certainly possible but not simple. If you are looking for the magical linq one liner, you are out of luck, thats not going to happen. – InBetween Jun 09 '20 at 14:08
  • 1
    How complicated can B and C be? Also, is there a Not operator? As was pointed out this is not trivial. – dbasnett Jun 09 '20 at 16:14
  • What do you think about; 1) checking all values in B and C if they are present in A, This way replacing all values with just "True" oder "False". Then 2) the remaining String has to be evaluated for True or False. I hope i could explain the idea. – fox Jun 09 '20 at 16:17
  • B and C can be little more compley then shown but no NOT operator. Just ANDs and ORs – fox Jun 09 '20 at 16:18

2 Answers2

1

What works is:

1) Checking all values in B and C if they are present in A, and replace them with "True" oder "False". Leads from:

B = "(ut2 | eg1) & ab1"

to

B = "(False| False) & True"

2) Evaluating this String.

For Evaluation I found this Method: https://stackoverflow.com/a/8476664/6552728

    Dim dt As New System.Data.DataTable()
    dt.Columns.Add("", GetType(Boolean))
    dt.Columns(0).Expression = "(False| False) & True"

    Dim r As System.Data.DataRow = dt.NewRow()
    dt.Rows.Add(r)
    Dim result As Boolean = DirectCast(r(0), Boolean)
    MsgBox(result.toString)

Thats maybe not very nice but it works. Is there a way evaluating logical expressions stored in String using LINQ Expression or Binary Expression? I tried for the last 2 hours but can't get it working...

    Dim exp As Expression(Of Func(Of String, Boolean)) = Function(p) p = "True Or False"
    Dim o As Object
    o = Expression.Lambda(exp).Compile().DynamicInvoke()
fox
  • 25
  • 8
  • 1
    To answer your question, only by using Roslyn Scripting. However there is a simple expression evaluator that can be used as a trick: `new DataTable().Compute("true or false", null)` returns `True`. – NetMage Jun 16 '20 at 20:44
1

StackOverflow isn't a code writing service, but some code is fun to write.

Here is a class that parses the test phrase into an Expression tree and then compiles the tree to return a Func to test a HashSet<string>:

public static class PhraseCompiler {
    public static Regex tokenRE = new Regex(@"\(|\)|&|\||\w+", RegexOptions.Compiled);
    static IEnumerable<string> Tokens(string phrase) {
        foreach (Match m in tokenRE.Matches(phrase))
            yield return m.Value;
    }

    static MethodInfo HSContainsMI = typeof(HashSet<string>).GetMethod("Contains");
    static Expression subCompile(IEnumerator<string> te, ParameterExpression g) {
        var stack = new Stack<Expression>();

        while (te.MoveNext()) {
            switch (te.Current) {
                case "(":
                    break;
                case ")":
                    goto exit_while;
                case "|":
                case "&":
                    var op = te.Current;
                    var lhs = stack.Pop();
                    var rhs = subCompile(te, g);
                    var opExpr = Expression.MakeBinary(op == "|" ? ExpressionType.OrElse : ExpressionType.AndAlso, lhs, rhs);
                    stack.Push(opExpr);
                    break;
                default:
                    var v = Expression.Constant(te.Current, typeof(string));
                    var test = Expression.Call(g, HSContainsMI, v);
                    stack.Push(test);
                    break;
            }
        }
    exit_while:

        return stack.Pop();
    }

    public static Func<HashSet<string>, bool> Compile(string phrase) {
        var g = Expression.Parameter(typeof(HashSet<string>), "g");

        var te = Tokens(phrase).GetEnumerator();
        var e = subCompile(te, g);

        var ef = Expression.Lambda<Func<HashSet<string>, bool>>(e, g);

        return ef.Compile();
    }
}

Given your initial values

var A = "ab1, eb4, gr3";

var B = "((ut2 | eg1) | gr3) & ab1";
var C = "ke2 & pa5";

Then you can convert the test value to a HashSet<string>:

var As = A.Split(", ").ToHashSet();

And then you can compile the tests:

var fB = PhraseCompiler.Compile(B);
var fC = PhraseCompiler.Compile(C);

and run the tests:

var testBresult = fB(As);
var testCresult = fC(As);

NOTE: The compiler does no error checking and is likely to throw an exception for bad test phrases.

NetMage
  • 26,163
  • 3
  • 34
  • 55
  • First let me say a big thank you! I learned a lot from that. But I think it is actually not evaluating correctly. For e.g. A = "ab1, eb4, gr3" and with B = "(ut2 | eb4) & ab1" this way B should return True, because of (False OR True) AND True, right? Also... var As = A.Split(", ").ToHashSet(); It should instead be: var As = A.Split(',').ToHashSet(); as split is expecting just one single character right? – fox Jun 10 '20 at 18:09
  • Okay it is because of the .Split Method. Changing to ',' and replacing all " " with "" in the ends up with correct result. – fox Jun 10 '20 at 18:31
  • @fox Given your input, it should have worked - perhaps your original string isn't exactly as you have in your question? – NetMage Jun 10 '20 at 23:21
  • Sorry for the late reply. Just found the error. Was on my side. Works perfectly, thank you very much! Learned a lot just by reading your code several times! – fox Jun 16 '20 at 16:58