Не могу правильно сформировать массив JS

У меня есть функционал для решения логических уравнений. Выглядит это примерно так :

  • Кнопка (добавить ПАРАМЕТР)
  • Кнопка ("("),
  • Кнопка (")"),
  • Кнопка ("И"),
  • Кнопка ("ИЛИ")

Кнопка Параметр включает в себя сравнение двух переменных (если val_id_first > val_id_second пример)

По клику на каждую из кнопок в пустой блок вставляется та или иная логическая операция. В конце я получаю json массив такого характера

{
 0:{ logic: "(" },
 1:{ id_one: "val_id_first",compare: " > ", id_two: "val_id_second" },
 2:{ logic: "&&" },
 3:{ id_one: "val_id_first",compare: " < ", id_two: "val_id_second" },
 4:{ logic: ")" }
}

Все идет по порядку нажатия на то или иное действие. Вопрос, как мне получить в одну строку все эти действия, с тем учетом, что бы я смог мои val_id_first и val_id_second использовать как ключ другого массива, data['val_id_second '] в последующих действиях. Буду рад любой помощи. Спасибо.


Ответы (3 шт):

Автор решения: Anton Shchyrov

const operators = [
  {logic: "("},
  {id_one: "val_id_first", compare: " > ", id_two: "val_id_second"},
  {logic: "&&"},
  {id_one: "val_id_first", compare: " < ", id_two: "val_id_second"},
  {logic: ")"}
];

const data = {
  "val_id_first": 10,
  "val_id_second": 20
};

function calcExp(exp) {
  const val1 = data[exp.id_one];
  const val2 = data[exp.id_two];
  switch (exp.compare) {
    case ' > ': return val1 > val2;
    case ' >= ': return val1 >= val2;
    case ' < ': return val1 < val2;
    case ' <= ': return val1 <= val2;
    case ' == ': return val1 == val2;
  }
}

function calc(ops, start) {
  const res = {
    val: undefined,
    end: undefined
  };
  let op;
  for (let idx = start; idx < ops.length; idx++) {
    let curVal;
    switch (ops[idx].logic) {
      case '(':
        const obj = calc(ops, idx + 1);
        curVal = obj.val;
        idx = obj.end;
        break;
      case ')':
        res.end = idx;
        return res;
      case '&&':
      case '||':
        op = ops[idx].logic;
        break;
      default:
        curVal = calcExp(ops[idx]);
    }
    if (curVal !== undefined) {
      switch (op) {
        case '&&':
          res.val = res.val && curVal;
          break;
        case '||':
          res.val = res.val || curVal;
          break;
        default:
          res.val = curVal;
      }
      op = undefined;
    }
  }
  return res;
}

console.log(calc(operators, 0).val);

→ Ссылка
Автор решения: yar85

Пример создания из объекта (в вопросе) либо строки выражения, либо функции (для того чтобы результат выражения можно было вычислить, подставив в него значения операндов):

const createCalcFunc = (exprObj, option) => {
  option = String(option).trim().toLowerCase(); 
  const returnExpressionOnly = (option === 'expression'), 
        useArgsObject = (option === 'argsobject'); 
  let parameters = new Set();
  const expression = Object.values(exprObj).map(part => {
    const tokens = [];
    for (let [key, token] of Object.entries(part)) {
      token = token.trim(); 
      if (/^id_/i.test(key)) {      // если токен выражения является операндом 
        if (useArgsObject)
          token = `args.${token}`; 
        else
          parameters.add(token);
      }
      tokens.push(token);
    }
    return tokens.join(' ');
  }).join(' ');
  if (returnExpressionOnly) return expression;
  if (useArgsObject) return new Function('args', `return ${expression};`);
  return new Function(...parameters, `return ${expression};`);
};


const testExpressionObj = {
  0: { logic: "(" },
  1: { id_one: "val_id_first", compare: " > ", id_two: "val_id_second" },
  2: { logic: "&&" },
  3: { id_one: "val_id_first", compare: " < ", id_two: "val_id_third" },
  4: { logic: ")" },
};

// 1. обычное использование этого "генератора функций", без опции - операнды выражения передаются параметрами вызова (в порядке их первого упоминания в выражении)
const testFunc = createCalcFunc(testExpressionObj);
console.log('1a:', testFunc(8, 4, 16));   // --> (8 > 4 && 8 < 16)   --> true
console.log('1b:', testFunc(32, 4, 16));  // --> (32 > 4 && 32 < 16) --> false

// 2. опция 'expression' - только получение выражения, без создания функции (для отладки выражений, или их вывода в UI)
const testExpr = createCalcFunc(testExpressionObj, 'expression'); 
console.log('2:', testExpr);              // --> "( val_id_first > val_id_second && val_id_first < val_id_third )"

// 3. опция 'argsObject' - для передачи функции операндов выражения в виде объекта (имена свойств которого соответствуют именам "переменных" в выражении)
const argsTestFunc = createCalcFunc(testExpressionObj, 'argsObject');
const args = { val_id_first: 9, val_id_second: 5, val_id_third: 42 }; 
console.log('3:', argsTestFunc(args));    // --> (9 > 5 && 9 < 42) --> true

В примере я изменил объект с частями выражения добавив третью переменную - и чтобы поинтереснее было, и потому что выражение из вопроса содержит логическую ошибку (из-за которой оно всегда возвращало бы false).


Несущественное:

Если зачем-то хочется получить на выходе массив из одного элемента (как указано в вопросе), то

const arr = [createCalcFunc(/*....*/)];

А если в режиме возврата строки выражения не нравятся (добавляемые для надежности) пробелы вокруг скобок, то

return expression.replace(/\s*([()])\s*/g, '$1');
→ Ссылка
Автор решения: Паша Харченко

В моем случае это сработало - спасибо @yar85 Я отличаю как бы это глупо не звучало операторы присваивания по длине xD Ах да, в результате я ловлю true или false как

 const logical_condition = this.logicCondition(val, rowNode.data);
 console.log(eval(logical_condition));
 

 function logicCondition(val, rowData) {
    return val.map(part => {
        const tokens = [];
        for (const [key, token] of Object.entries(part)) {
            if (token.length > 2) {
                tokens.push(rowData[token]);
            } else {
                switch (token) {
                    case '=' :
                        tokens.push('===');
                        break;
                    case '<>' :
                        tokens.push('!==');
                        break;

                    default:
                        tokens.push(token);
                }
            }
        }
        return tokens.join(' ');
    }).join(' ');
}
→ Ссылка