8

Does anybody know if there is a way to evaluate a string containing boolean-logic at runtime? My logic, can only contain three operators and parenthesis (AND, OR, NOT). I can even make the string look like this: true && false && (true || !true)

I don't have the bandwidth to build a full-on parser... Any ideas?

PS: I saw some answers that pointed to the use of an API but I don't have access to use the Toolkit API in the environment this will get deployed.

12

I got a fairly simple one working. It might not perform as well as a more sophisticated option, but it is easy to understand.

Logic

public class BooleanExpression
{
    static Boolean orJoin(String x, String y) { return evaluate(x) || evaluate(y); }
    static Boolean andJoin(String x, String y) { return evaluate(x) && evaluate(y); }
    static Boolean isSimpleExpression(String x) { return x == 'true' || x == 'false'; }
    static String simplify(String x)
    {
        x = x.trim();
        while (x.contains('('))
        {
            String sub = x.substringAfterLast('(').substringBefore(')');
            x = x.replace('(' + sub + ')', String.valueOf(evaluate(sub)));
        }
        return x;
    }
    static Boolean evaluate(String x)
    {
        x = simplify(x);
        if (!isSimpleExpression(x))
        {
            if (x.contains('&&')) return andJoin(x.split('&&', 2));
            if (x.contains('||')) return orJoin(x.split('||', 2));
            if (x.startsWith('!')) return !evaluate(x.substring(1));
        }
        return Boolean.valueOf(x);
    }
}

Tests

I know you can just use assert(evaluate(expression)), but that feels kind of dirty here.

@IsTest
class BooleanExpressionTests
{
    static testMethod void testExpressions()
    {
        system.assertEquals(true, evaluate('true'));
        system.assertEquals(false, evaluate('!true'));
        system.assertEquals(true, evaluate('!false'));
        system.assertEquals(false, evaluate('false'));
        system.assertEquals(false, evaluate('true && !true'));
        system.assertEquals(true, evaluate('false || !false'));
        system.assertEquals(true,
            evaluate('false || (false || (!true || (false || true)))'));
    }
}
| improve this answer | |
  • 1
    This is perfect, it won't do 100% but it gets me far enough down the rabbit hole to complete it myself. Thanks! – Sebastian Kessel Mar 7 '16 at 23:26
  • For the record, I could recursively call your function with everything between ( and ). I think that should work. – Sebastian Kessel Mar 7 '16 at 23:27
  • Recursion deepness is my biggest worry, but in my use-cases it won't be a problem. I'll need to think on this a bit, but I think I can make it work. – Sebastian Kessel Mar 7 '16 at 23:33
  • 1
    Wow. I JUST finished testing my solution, almost identical to yours. Here it is... check out the simplify method. gist.github.com/sebastiankessel/838773f98b33d9d24eeb – Sebastian Kessel Mar 8 '16 at 0:03
  • 1
    @SebastianKessel I thought that approach was at risk to replacing other matching substrings, but on second thought... I tightened up my presentation a bit. – Adrian Larson Mar 8 '16 at 0:12
9

One more way may be using Reverse Polish Notation for evaluation of expressions without recursion and for linear time. My solution is just simplified version of this one.

Example of implementation some comments:

public class RPN {
    //map with priorities    
    public static Map<String,Integer> OperatorsPriorities = new Map<String,Integer>{
         '*' =>  2, // AND binary
         '+' =>  1,// OR binary
         '(' => -1, //
         ')' => -2,
         '-' => 3  //NOT - unary
    };

    public static Boolean evaluateString(String param){
        String rpn_string  = buildRPN(param);
        return evalRPN(rpn_string);
    }

    public static string buildRPN(String param){
        param = param.replace('true','t').replace('false','f').replace(' ','')
            .replace('&&','*').replace('||','+').replace('!','-');
        //to make input string smaller, do some nice replacements
        List<String> stack = new List<String>();//represent stack
        String result = '';//Reverse Polish Notation string
        Integer counter = 0;//current character position
        while(counter < param.length()){//go through input string
            String p = param.substring(counter,counter+1);//get current character
            if (p == 't' || p == 'f'){//if that is a value
                result += p;//add to RPN
            }else{
                if (OperatorsPriorities.get(p) == -1){//if that is open bracket
                    stack.add(p);//add to stack
                }else if(OperatorsPriorities.get(p) == -2){//if that is closed bracked
                    while(stack.get(stack.size() -1) != '('){//pop stack until open bracket
                        result += stack.remove(stack.size() - 1);
                    }
                    stack.remove(stack.size() - 1);//remove bracket from stack
                }else {//if that is operator
                    while (   stack.size() > 0 
                           && OperatorsPriorities.get(p) <= OperatorsPriorities.get(stack.get(stack.size() - 1))){
                        result += stack.remove(stack.size() - 1);//pop from stack to RPN until operator with lower priority meet
                    }
                    stack.add(p);
                }
            }
            counter++;
        }
        while(stack.size()> 0){//pop stack to RPN result string
            result += stack.remove(stack.size() - 1);
        }
        return result;
    }


    public static string opposite(string param){
        return (param == 't')? 'f': 't';
    }

    public static boolean evalRPN(string prn){
        List<String> elements = prn.split('');
        Integer position = 0;
        while(elements.size() > 1){//evaluate rpn string unless 1 value left
            String element = elements.get(position);//get current position
            if (element == 't' || element == 'f'){//if that is value - go forward
                position++;
            }else{
                if (element == '-'){//unary operator - replace previous value and remove it
                    elements.set(position-1,opposite(elements.get(position-1)));
                    elements.remove(position);
                    position--;
                }else if (element == '*'){//binary operator - replace prev prev value and remove prev and current 
                    String par1 = elements.get(position - 2);
                    String par2 = elements.get(position - 1);
                    String res = (par1 == 'f' || par2 == 'f')? 'f' : 't';
                    elements.set(position-2,res);
                    elements.remove(position);
                    elements.remove(position-1);
                    position -= 2;
                }else if (element == '+'){//another binary operator
                    String par1 = elements.get(position - 2);
                    String par2 = elements.get(position - 1);
                    String res = (par1 == 't' || par2 == 't')? 't' : 'f';
                    elements.set(position-2,res);
                    elements.remove(position);
                    elements.remove(position-1);
                    position -= 2;
                }
            }
        }
        return  (elements.get(0) == 't')?true:false;
    }
}
| improve this answer | |
  • Thanks for this, but RPN Won't help me, I have a routine that writes this string and i can't change it. Besides, humans will read it and RPN is not as easy to understand. – Sebastian Kessel Mar 8 '16 at 0:44
  • Certainly harder for me to wrap my head around. Still, interesting approach. +1 – Adrian Larson Mar 8 '16 at 0:45
  • Human doesn't need to read that (except in errors case). This consists of 2 modules, first one build rpn, second evaluate it. And, in case of understanding this simple version, it would be easier to build more complex one in future – kurunve Mar 8 '16 at 0:47
  • 4
    +1 for RPN - along with its friend, the HP-45 calculator - without which I couldn't have done all my engineering problem sets as rapidly. – cropredy Mar 8 '16 at 0:50
  • @kurunve, in my use case, I absolutely need a human to read it. Thanks, but this is not what I was looking for. – Sebastian Kessel Mar 8 '16 at 15:23
1

A couple years back I converted an open source java boolean expression evaluator into Apex. The company I was working for at the time has given permission for me to release it to the public, so hopefully this will be helpful to someone.

https://github.com/mattsmall/apex-expressions

| improve this answer | |
0

Just adding this as one more solution of this requirement. Here I am just trying to get advantage of Boolean calculation and replaceAll method of string class. As a result, it becomes very fast in execution and we can use it in trigger as well.

Example:

String expression = 'false and (false or (true and (false or true)))';

//Run this one and check the CPU time and compare with other solution...apparently it would be faster BooleanExpression.evaluateExpression(expression);

public class BooleanExpressionCalculation{

public static boolean evaluateExpression(String expression){
    system.debug(expression.containsIgnoreCase('false')+'__'+expression.containsIgnoreCase('true'));
    if(expression.containsIgnoreCase('false') == false){ return true;}
    if(expression.containsIgnoreCase('true') == false){ return false;}
    expression = runeval(expression);
    if(expression == 'true') return true;
    return false;
}

public static string runeval(String expression){
    if(expression.contains('true and true')||expression.contains('true and false')||expression.contains('false and true')||expression.contains('false and false')){
        expression = BooleanExpressionCalculation.replaceANDLogic(expression);
    }
    if(expression.contains('true or true')||expression.contains('true or false')||expression.contains('false or true')||expression.contains('false or false')){
        expression = BooleanExpressionCalculation.replaceORLogic(expression);
    }
    system.debug(expression+'___');   
    if(expression.contains('(true)')){     
        expression = replaceleftBrackets(expression);
    }
    if(expression.contains('(false)')){
        expression = replaceRightBrackets(expression);
    }
    system.debug(expression+'___');
    if(expression.contains('(')|| expression.contains(')') || expression.contains('or')|| expression.contains('and')){
        expression = runeval(expression);
    }
    system.debug(expression+'___final');
    return expression;
}

public static string replaceleftBrackets(string expression){
    expression = expression.replaceAll('\\(true\\)', 'true');
    if(expression.contains('(true)')){
        expression = replaceRightBrackets(expression);
    }
    return expression;
}
public static string replaceRightBrackets(string expression){
    expression = expression.replaceAll('\\(false\\)', 'false');
    if(expression.contains('(false)')){
        expression = replaceRightBrackets(expression);
    }
    return expression;
}

public static string replaceANDLogic(String exp){
    exp = exp.replaceAll('true and true', 'true');
    exp = exp.replaceAll('false and true', 'false');
    exp = exp.replaceAll('true and false', 'false');
    exp = exp.replaceAll('false and false', 'false');
    if(exp.contains('true and true')||exp.contains('true and false')||exp.contains('false and true')||exp.contains('false and false')){
        exp = replaceANDLogic(exp);
    }        
    return exp;
}
public static string replaceORLogic(String exp){
    exp = exp.replaceAll('true or true', 'true');
    exp = exp.replaceAll('false or true', 'true');
    exp = exp.replaceAll('true or false', 'true');
    exp = exp.replaceAll('false or false', 'false');
    if(exp.contains('true or true')||exp.contains('true or false')||exp.contains('false or true')||exp.contains('false or false')){
        system.debug('ayub___');
        exp = replaceORLogic(exp);
    }
    return exp;
}}

Source Code Link: https://gist.github.com/ayub-ansari/d689fe390e0f7350ce6600a09335981f

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.