【问题标题】:Evaluate expression tree in Javascript在 Javascript 中评估表达式树
【发布时间】:2020-12-10 02:49:24
【问题描述】:

我的输入由嵌套的逻辑表达式对象组成

例如:

var obj = {
  'OR': [
      {
        'AND': [
            false, true, true
        ]
      },
      {
        'OR': [
            true, false, false, {
                'AND': [true, true]
            }
        ]
      },
      true
  ]
};

相当于((false && true && true) || (true || false || false || (true && true)) || true)

我们需要写一个函数来计算这个

方法:

进入最内层,先求值,移到顶部

var expressionEvaluator = function(opArr){
            var hasChildObjects = function(arr){
                if(Array.isArray(arr)){
                    return arr.some(function(obj){
                        return typeof(obj) === 'object';
                    });
                }
                else if(typeof(arr) === 'object'){
                    return true;
                }
            };
            var evaluateArr = function(list, operator){
                var result;
                if(operator === 'AND'){
                    result = true;
                    for(var i = 0; i<list.length; i++){
                        if(!list[i]){
                            result = false;
                        }
                    }
                }
                else if(operator === 'OR'){
                    result = false;
                    for(var i = 0; i<list.length; i++){
                        if(list[i]){
                            result = true;
                        }
                    }
                }
                return result;
            };
            var iterate = function(opArr){
                Object.keys(opArr).forEach(function(k){
                    if(hasChildObjects(opArr[k])){
                        iterate(opArr[k]);
                    }
                    else{
                        opArr = evaluateArr(opArr[k], k);
                    }
                });
            };
            iterate(opArr);
            return result;
        }

我能够到达最里面的对象并评估它,但不能回到最顶层并评估整个表达式对象。

【问题讨论】:

    标签: javascript recursion expression-trees


    【解决方案1】:

    你可以使用一个简单的递归函数。

    • 如果当前对象有OR键,则检查数组中的some是否为truthy
    • 如果AND,检查every项目是否为truthy
    • 如果数组中的一项是对象,则递归调用对象上的函数以获取其

    const input={OR:[{AND:[false,true,true]},{OR:[true,false,false,{AND:[true,true]}]},true]};
    
    function evaluate({ OR, AND }) {
      if (OR)
        return OR.some(c => typeof c === 'object' ? evaluate(c) : c)
      if (AND)
        return AND.every(c => typeof c === 'object' ? evaluate(c) : c)
    }
    
    console.log(evaluate(input))

    由于回调函数是一样的,你也可以得到一个变量的操作并动态调用它:

    function evaluate({ OR, AND }) {
      const array = OR ?? AND,
            operation = OR ? 'some' : 'every';
      
      return array[operation](c => typeof c === 'object' ? evaluate(c) : c)
    }
    

    const evaluate = ({ OR, AND }) => OR ? OR.some(callback) : AND.every(callback),
          callback = c => typeof c === 'object' ? evaluate(c) : c
    

    【讨论】:

    • 甚至可以(OR ?? AND)[OR ? 'some' : 'every'](...),虽然我知道这不是代码高尔夫
    • @adiga 我们是否还可以为任何键、数组值添加空/未定义检查?
    • @user544079 尝试用Object(c) === c替换typeof c === 'object'
    【解决方案2】:

    这里主题的另一个变体:

    const evaluate = (tree) =>
      typeof tree === 'boolean'
        ? tree
      : 'OR' in tree
        ? tree .OR .some (evaluate)
      : 'AND' in tree
        ? tree .AND .every (evaluate)
      : false // or throw an error?
    
    const tree = {OR: [{AND: [false, true, true]}, {OR: [true, false, false, {AND: [true, true]}]}, true]}
    
    console .log (evaluate (tree))

    evaluate 返回提供的值(如果它是布尔值)。否则,它会检查'OR''AND' 节点,并适当地处理它们。对于非良好的树木,最后有一个包罗万象的东西。这里我返回false,但你可能会抛出一个错误,返回null,或者别的什么。

    如果您不需要全部内容,可以将其简化为单行:

    const evaluate = (tree) =>
      typeof tree == 'boolean' ? tree: tree.OR ? tree.OR .some (evaluate) : tree.AND .every (evaluate)
    

    但我担心树状结构。当有些对象只能具有一个属性时,我总是担心。感觉数组是一个更好的设计。

    这种替代方式感觉更干净:

    const tree = [
      'OR', 
      [
        ['AND', [false, true, true]], 
        ['OR', [true, false, false, ['AND', [true, true]]]], 
        true
      ]
    ]
    

    这可以用类似的代码进行评估:

    const evaluate = (tree) =>
      typeof tree == 'boolean' ? tree : tree [1] [tree [0] === 'OR' ? 'some' : 'every'] (evaluate)
    

    更新:customcommander 的A comment 指出,即使是这种数组格式也过于复杂。

    如果相反,我们正在处理这样的事情:

    const tree = [
      'OR', 
      ['AND', false, true, true], 
      ['OR', true, false, false, ['AND', true, true]], 
      true
    ]
    

    那么我们可以使用这样的版本:

    const evaluate = (tree) =>
      typeof tree === 'boolean' 
        ? tree 
        : tree .slice (1) [tree [0] === 'OR' ? 'some' : 'every'] (evaluate)
    

    【讨论】:

    • +1 用于建议不同的数据结构。也许它可以进一步精简:[OR, true, [AND, true, false]]?
    • @customcommander:是的,我认为这样会更好。
    • @customcommander:添加了一个具有该格式的版本。
    【解决方案3】:

    不确定这是否是一个好的解决方案,但我认为我们可能会有点“懒惰”并避免不必要的递归,这可能会或可能不会帮助取决于表达式树的大小。

    在以下表达式中,由于 C 已经为真,因此无需同时计算 AB

    {OR: [{/* ... */}, {/* ... */}, true]}
    //    ^            ^            ^
    //    A            B            C
    

    同样没有必要同时评估 AB 因为 C 已经是假的:

    {AND: [{/* ... */}, {/* ... */}, false]}
    //     ^            ^            ^
    //     A            B            C
    

    考虑到这一点,我想出了以下代码:

    const lazy_or = exp => exp.find(e => e === true);
    const lazy_and = exp => exp.find(e => e === false);
    
    const evaluate =
      exp =>
          typeof exp === "boolean"                ? exp
        : exp.OR && lazy_or(exp.OR)               ? true
        : exp.OR                                  ? exp.OR.some(evaluate)
        : exp.AND && lazy_and(exp.AND) === false  ? false
                                                  : exp.AND.every(evaluate);
    

    【讨论】:

    • 我对优化概念的担忧是,如果我们立即检查早期的,我们有时会更快地短路:{OR: [false, {AND: [true, true]}, false, false, false, ... (a million times)... false]}。在不知道布尔值和对象的平衡的情况下,这似乎只是猜测。
    • @ScottSauyet 这很公平。我不认为我的答案是一个特别好的答案,但我认为提及它会很有用(没有其他人这样做,也许是有充分理由的)。但是对于{OR: [false, {OR: [ false, {OR: [ false, { ...(deeply nested) ... }]}]}, true]} 也可以提出同样的论点。你是对的,虽然这只是猜测和希望最好的结果。
    • 是的,这就是我的意思。仅当人们对数据了解得更多时,这种优化才有用。
    【解决方案4】:
    function evalOBJ(obj) {
        let result = true;
        if (obj.OR) {
            result = false;
            for (const v of obj.OR) {
                if (typeof v === 'object') {
                    result = result || evalOBJ(v);
                } else {
                    result = result || v;
                }
            }
        } else if (obj.AND) {
            for (const v of obj.AND) {
                if (typeof v === 'object') {
                    result = result && evalOBJ(v);
                } else {
                    result = result && v;
                }
            }
        }
        return result;
    }
    

    【讨论】:

      【解决方案5】:

      这与adiga's answer 基本相同,但变体使用evaluate 和运算符函数之间的相互递归。主要好处是遍历树evaluate 的算法与实际使用的运算符之间的分离。如果需要,只需将它们添加到 operators 即可更轻松地重构以处理其他操作。

      const operators = {
        "OR" : arr => arr.some(evaluate),
        "AND": arr => arr.every(evaluate),
      }
      
      function evaluate(input) {
        if (typeof input !== "object") 
          return input;
        
        const [op, value] = Object.entries(input)[0];
        return operators[op](value);
      }
      
      test(true);
      test(false);
      test({"OR": [false, true]});
      test({"OR": [false, false]});
      
      test({"AND": [true, true]});
      test({"AND": [false, true]});
      
      test({
        'OR': [
            {
              'AND': [
                  false, true, true
              ]
            },
            {
              'OR': [
                  true, false, false, {
                      'AND': [true, true]
                  }
              ]
            },
            true
        ]
      })
      
      function test(input) {
        console.log(`evaluating: ${JSON.stringify(input, undefined, 2)}
        result: ${evaluate(input)}`)
      }

      为了展示扩展的便利性,我们可以将其转换为也处理数学运算,只需添加处理加法和乘法的运算符:

      const operators = {
        "OR" : arr => arr.some(evaluate),
        "AND": arr => arr.every(evaluate),
        "+": arr => arr.reduce((a, b) => a + evaluate(b), 0),
        "*": arr => arr.reduce((a, b) => a * evaluate(b), 1),
      }
      
      function evaluate(input) {
        if (typeof input !== "object") 
          return input;
        
        const [op, value] = Object.entries(input)[0];
        return operators[op](value);
      }
      
      test({"+": [2, 3]});
      test({"*": [2, 3]});
      test({
        '+': [
            {
              '*': [
                  2, 2, 5
              ]
            },
            {
              '+': [
                  6, 4, 3, {
                      '*': [1, 7]
                  }
              ]
            },
            2
        ]
      })
      
      function test(input) {
        console.log(`evaluating: ${JSON.stringify(input, undefined, 2)}
        result: ${evaluate(input)}`)
      }

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-01-16
        • 2013-11-11
        • 1970-01-01
        • 2016-08-03
        • 1970-01-01
        相关资源
        最近更新 更多