Как проложить свой путь через дерево S-выражений, чтобы удовлетворить тестовый случай

#javascript #s-expression

#javascript #s-выражение

Вопрос:

Итак, у меня есть этот массив S-выражений

   const condition = [
  'OR',
  [
    'AND',
    ['==', '$State', 'Alabama'],
    ['==', '$Profession', 'Software development']
  ],
  ['==', '$Undefined', ''],
  [
    'AND',
    ['==', '$State', 'Texas']
  ],
  [
    'OR',
    [
      'OR',
      ['==', '$Profession', 'Tradesperson']
    ]
  ]
]
  

У меня есть этот тестовый пример и функция, которые должны быть удовлетворены

 const testCases = [
  [{'State': 'Alabama', 'Profession': 'Software development'}, true],
  [{'State': 'Texas'}, true],
  [{'State': 'Alabama', 'Profession': 'Gaming'}, false],
  [{'State': 'Utah'}, false],
  [{'Profession': 'Town crier'}, false],
  [{'Profession': 'Tradesperson'}, true],
  [{}, false]
]

for (const [index, [context, expected]] of testCases.entries()) {
  console.log(
    evaluate(condition as Condition, context) === expected
      ? `${index} ok`
      : `${index} FAIL`
  )
}
  

Что у меня есть до сих пор, так это

 function evaluate (condition: Condition, context: Context): boolean {
  if (isLogicalCondition(condition)) {
    const [operator, ...conditions] = condition

  }

  if (isComparisonCondition(condition)) {
    const [operator, variable, value] = condition
    
  }

  return false
}
  

Функции, типы и переменные являются

 type Context = {
  [k: string]: string | undefined
}

enum LogicalOperator {
  And = 'AND',
  Or = 'OR'
}

enum ComparisonOperator {
  Eq = '=='
}

type Operator = LogicalOperator | ComparisonOperator 
type LogicalCondition = [LogicalOperator, ...Array<Condition>]
type Variable = string
type Value = string
type ComparisonCondition = [ComparisonOperator, Variable, Value]
type Condition = LogicalCondition | ComparisonCondition

function isLogicalCondition (condition: Condition): condition is LogicalCondition {
  return Object.values(LogicalOperator).includes(condition[0] as LogicalOperator)
}

function isComparisonCondition (condition: Condition): condition is ComparisonCondition {
  return Object.values(ComparisonOperator).includes(condition[0] as ComparisonOperator)
}
  

Мой вопрос в том, как мне даже подумать об этом достаточно абстрактно, чтобы решить эту проблему, чтобы выполнить тест без жесткого кодирования результатов теста? Я полностью потерян…

Ответ №1:

Вы должны использовать рекурсию. Оператор «ИЛИ» преобразуется в some вызов метода, а «И» — в every вызов метода.

Для равенства вы должны иметь дело с любым префиксом «$», и в этом случае вам нужно искать значение в объекте контекста. Было бы неплохо не предполагать, что первый аргумент всегда имеет «$» …, поэтому я предлагаю использовать mapper для обоих аргументов, каждый раз имея дело с потенциальным «$».

Вы могли бы использовать эту функцию:

 function evaluate(condition, context) {
    let [operator, ...arguments] = condition;
    if (operator === "==") {
        return arguments.map(arg => arg[0] === "$" ? context[arg.slice(1)] : arg)
                        .reduce(Object.is);
    }
    if (operator === "OR") {
        return arguments.some(argument => evaluate(argument, context));
    }
    if (operator === "AND") {
        return arguments.every(argument => evaluate(argument, context));
    }
    throw "unknown operator "   operator;
}
  

Запуск тестовых примеров:

 function evaluate(condition, context) {
    let [operator, ...arguments] = condition;
    if (operator === "==") {
        return arguments.map(arg => arg[0] === "$" ? context[arg.slice(1)] : arg)
                        .reduce(Object.is);
    }
    if (operator === "OR") {
        return arguments.some(argument => evaluate(argument, context));
    }
    if (operator === "AND") {
        return arguments.every(argument => evaluate(argument, context));
    }
    throw "unknown operator "   operator;
}


const condition = [
  'OR',
  [
    'AND',
    ['==', '$State', 'Alabama'],
    ['==', '$Profession', 'Software development']
  ],
  ['==', '$Undefined', ''],
  [
    'AND',
    ['==', '$State', 'Texas']
  ],
  [
    'OR',
    [
      'OR',
      ['==', '$Profession', 'Tradesperson']
    ]
  ]
]

const testCases = [
  [{'State': 'Alabama', 'Profession': 'Software development'}, true],
  [{'State': 'Texas'}, true],
  [{'State': 'Alabama', 'Profession': 'Gaming'}, false],
  [{'State': 'Utah'}, false],
  [{'Profession': 'Town crier'}, false],
  [{'Profession': 'Tradesperson'}, true],
  [{}, false]
]

for (const [index, [context, expected]] of testCases.entries()) {
  console.log(
    evaluate(condition, context) === expected
      ? `${index} ok`
      : `${index} FAIL`
  )
}