簡體   English   中英

評估 Javascript 中的表達式樹

[英]Evaluate expression tree in Javascript

我的輸入由嵌套的邏輯表達式對象組成

前任:

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

相當於((false && true && true) || (true || false || false || (true && true)) || true)

我們需要編寫一個 function 來計算這個

方法:

Go 到最內層並先評估它,移動到頂部

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;
        }

我能夠到達最里面的 object 並對其進行評估,但不能回到最頂層並評估整個表達式 object。

您可以使用簡單的遞歸 function。

  • 如果當前truthyOR鍵,則檢查數組中的some項是否為真。
  • 如果AND ,檢查every項目是否truthy
  • 如果數組中的一項是 object,則遞歸調用 object 上的 function 以獲取其

 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

這里主題的另一個變體:

 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))

如果是 boolean, 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 的評論指出,即使是這種數組格式也過於復雜。

相反,我們正在處理這樣的事情:

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)

不確定這是否是一個好的解決方案,但我認為我們可能會有點“懶惰”並避免不必要的遞歸,這可能會或可能不會幫助取決於表達式樹的大小。

在以下表達式中,無需同時評估AB ,因為C已經為真:

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

同樣,由於C已經為假,因此無需同時評估AB

{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);
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;
}

這與阿迪加的答案基本相同,但變體使用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)}`) }

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM