How to make your way through an S-expression tree to satisfy a test case

109 Views Asked by At

So I have this S-expression array

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

I have this test case and function that need to be satisfied

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

What I have so far is

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

  }

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

  return false
}

The functions, types and variables are

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

My question is how do I even think about this in an abstract enough way to solve this issue to satisfy the test without hardcoding results against the test? I am totally lost...

1

There are 1 best solutions below

0
trincot On BEST ANSWER

You should use recursion. The "OR" operator translates to a some method call, and "AND" to a every method call.

For equality, you should deal with any "$" prefix, in which case you have to look up the value in the context object. It would be nice to not assume that the first argument always has the "$"..., so I propose to use a mapper on both arguments, each time dealing with the potential "$".

You could use this function:

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

Running the test cases:

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