3

I've struggle with following task:

Assuming that I have a list with names and values (like a Dictionary). I need to provide user of Web interface a field where he can write a query, checking for existence of specific names and values on that list.

For example, I've got a following list:

A = 2, B = 4, D = 0

Users want to query this list like that (don't mind syntax, it's just a pseudocode)

  • A == 2 && D => this returns true, as A exists and it's value is 2 and D also exists.
  • (A && B) || C => this returns true, as both A and B exists on the list.
  • A && !B => this returns false, as A exists on the list but B as well (but B shouldn't)

I've been looking on dynamic LINQ, but it seems that I can evaluate only one object at a time (can't check if object exists on the list and then ask if another one doesn't). Does anyone knows any materials or links that would be useful? Thanks

komsky
  • 1,568
  • 15
  • 22
  • Not entirely sure what you're asking for... You can have ORs and ANDs with LINQ... – Alex Jan 28 '16 at 12:17
  • 2
    You will probably have to implement your own Boolean expression parser; [this question](http://stackoverflow.com/q/17568067/87698) could be a good starting point. – Heinzi Jan 28 '16 at 12:18
  • 2
    Why don't you show some code instead of pseudo.. it would be easier to post solutions. – tede24 Jan 28 '16 at 12:22
  • You can use Expression [Trees to Build Dynamic Queries](https://msdn.microsoft.com/library/bb882637%28v=vs.110%29.aspx) – Artavazd Balayan Jan 28 '16 at 12:22
  • Are users inputting this query as text? Then you need to parse which is hard. – usr Jan 28 '16 at 12:45
  • Yes, user provide this as a text and I need to parse it. More examples? The options are limitless, since I'm looking to cover whole boolean logic. Like (!A && B) || (C || (!D && F) etc... – komsky Jan 28 '16 at 12:51

3 Answers3

3

not sure whether I have understood your requirement...

is this what you are asking for?

Dictionary<string, int> nameValuePairs = new Dictionary<string, int>();
        nameValuePairs.Add("A", 2);
        nameValuePairs.Add("B", 4);
        nameValuePairs.Add("D", 0);


        //A == 2 && D => this returns true, as A exists and it's value is 2 and D also exists
        int d = 0;
        if (nameValuePairs.ContainsKey("A") && nameValuePairs.TryGetValue("D", out d) && nameValuePairs.ContainsKey("D"))
        {
            Console.WriteLine("Test 1: True");
        }
        else
        {
            Console.WriteLine("Test 1: False");
        }

        //(A && B) OR C => this returns true, as both A and B exists on the list
        if ((nameValuePairs.ContainsKey("A") && nameValuePairs.ContainsKey("B")) || nameValuePairs.ContainsKey("C"))
        {
            Console.WriteLine("Test 2: True");
        }
        else
        {
            Console.WriteLine("Test 2: False");
        }

        //A && !B => this returns false, as A exists on the list but B as well (but B shouldn't)
        if (nameValuePairs.ContainsKey("A") && !nameValuePairs.ContainsKey("B")) 
        {
            Console.WriteLine("Test 3: True");
        }
        else
        {
            Console.WriteLine("Test 3: False");
        }
Rajes
  • 1,036
  • 10
  • 15
  • Sorry, Rajes, not really. Your code seems correct, but it must be generated on the fly. These were just examples, as user might come with !A || ((B&&D) || F) and so on. I'm looking for a generic approach. – komsky Jan 28 '16 at 12:48
0

Main idea - you will create dynamic code (look at GenerateCode function) just as string (it will be analog of your expression) and then compile it to assembly. Then by reflection you will take specific method Calculate and execute it. I created Wrapper class (each items from dictionary and digits will be wrapped into it), because operations like "&" and "|" impossible implement for int type.

Attention: Instead of "&&", "OR" you should use "&", "|" tokens or replace them after retrieving from client side before calculating.

Code:

using Microsoft.CSharp;
using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;

namespace ConsoleApplication5
{
    public class Program
    {
        public static string GenerateCode(Dictionary<string, int> dict, string expres)
        {
            var r = new Regex(@"-?\d+");
            foreach (var item1 in r.Matches(expres))
                expres = expres.Replace(item1.ToString(), string.Format("(new Wrapper({0}))", item1.ToString()));

            r = new Regex("[A-Z]");
            var res = "";
            var areadyDone = new List<string>();

            foreach (var item in r.Matches(expres))
            {
                var key = item.ToString();
                if (!areadyDone.Contains(key))
                {
                    res += string.Format("var {0} = new Wrapper({1});\n", item, dict.ContainsKey(key) ? dict[key].ToString() : "");
                    areadyDone.Add(key);
                }
            }

            return string.Format("{0}return {1};", res, expres);
        }

        public static bool GetAnswer(Dictionary<string, int> dict, string expres)
        {
            string code = @"
using System;

    namespace First
    {

public class Wrapper
    {
        public int value;
        public bool exist = false;
        public Wrapper(int value)
        {
            this.value = value;
            this.exist = true;
        }
        public Wrapper()
        {
        }

        private static bool wrap(Wrapper c1, Wrapper c2, bool cond)
        {
            return (c1 & c2) ? cond : false;
        }

        public static bool operator &(Wrapper c1, Wrapper c2)
        {
            return c1.exist && c2.exist;
        }
        public static bool operator |(Wrapper c1, Wrapper c2)
        {
            return c1.exist || c2.exist;
        }
        public static bool operator !(Wrapper c1)
        {
            return !c1.exist;
        }
        public static implicit operator bool(Wrapper d)
        {
            return d.exist;
        }

        public static bool operator >(Wrapper c1, Wrapper c2)
        {
            return wrap(c1, c2, c1.value > c2.value);
        }
        public static bool operator <(Wrapper c1, Wrapper c2)
        {
            return wrap(c1, c2, c1.value < c2.value);
        }
        public static bool operator >=(Wrapper c1, Wrapper c2)
        {
            return wrap(c1, c2, c1.value >= c2.value);
        }
        public static bool operator <=(Wrapper c1, Wrapper c2)
        {
            return wrap(c1, c2, c1.value <= c2.value);
        }

        public static bool operator ==(Wrapper c1, Wrapper c2)
        {
            return wrap(c1, c2, c1.value == c2.value);
        }
        public static bool operator !=(Wrapper c1, Wrapper c2)
        {
            return wrap(c1, c2, c1.value != c2.value);
        }

        public override bool Equals(object obj)
        {
            return base.Equals(obj);
        }

        public override int GetHashCode()
        {
            return base.GetHashCode();
        }

    }


        public class Program
        {
            public static bool Calculate()
            {
            " + GenerateCode(dict, expres) + @"
            }

            public static void Main()
            {  
            }

        }
    }";
            var provider = new CSharpCodeProvider();
            var parameters = new CompilerParameters();

            parameters.GenerateInMemory = true;
            parameters.GenerateExecutable = true;

            var results = provider.CompileAssemblyFromSource(parameters, code);
            if (results.Errors.HasErrors)
            {
                var sb = new StringBuilder();
                foreach (CompilerError error in results.Errors)
                    sb.AppendLine(String.Format("Error ({0}): {1}", error.ErrorNumber, error.ErrorText));
                throw new InvalidOperationException(sb.ToString());
            }
            else
            {
                var assembly = results.CompiledAssembly;
                var program = assembly.GetType("First.Program");
                var main = program.GetMethod("Calculate");
                return (bool)main.Invoke(null, null);
            }
        }

        public static void Main()
        {
            var dict = new Dictionary<string, int>();
            dict.Add("A", 23);
            dict.Add("B", 4);
            dict.Add("F", 5);

            Console.WriteLine(GetAnswer(dict, "(C > -5) | (A >= 10 & B)"));
        }
    }
}
Slava Utesinov
  • 13,410
  • 2
  • 19
  • 26
0

All right, actually Heinzi answered my question, pointing there's a question that answers my problem: How to parse a boolean expression and load it into a class?

I've reproduced code provided there and it works flawlessly. No dynamic code (what is far too hard for me), simple binary trees.

Thank you all for the involvement.

Community
  • 1
  • 1
komsky
  • 1,568
  • 15
  • 22