I want to store arbitrary mathematical expression with basic operations (+, -, *, /, ^, sqrt, grouping, etc…) and id placeholders as a string.
const json = {
“formula”: “{{a45bc2a1-ed82-4ccd-a455-f7959e875aad}}+({{f6c2ef2b-a4fa-4cfb-b62d-d0d7c3e266d9}}*{{335563ad-a715-47b9-8e54-2b8553768168}})”
}
Ids are mapped to arrays such as:
const map = {
“a45bc2a1-ed82-4ccd-a455-f7959e875aad”: [1, 2, 3, 4, 5],
“f6c2ef2b-a4fa-4cfb-b62d-d0d7c3e266d9”: [10, 20, 30, 40, 50],
“335563ad-a715-47b9-8e54-2b8553768168”: [1, 2, 3, 4, 5]
}
How can I achieve this? eval() doesn’t work with vectors and I don’t know how to parse arbitrary formula to do element-wise operations
Result should be:
[11, 42, 93, 164, 255]
Since you're, in fact, developing a programming language, you'll going to need a compiler (=translate string expressions into an AST) and a runtime (=evaluate AST given inputs and predefined bindings). Here's some code to get you started. It only supports a limited grammar expression = term | term op expression and doesn't handle any errors:
// "compiler"
function parse(str) {
return expr([...str])
}
function expr(chars) {
let node = term(chars)
if (chars.length)
node = {
op: chars.shift(),
left: node,
right: expr(chars)
}
return node
}
function term(chars) {
let str = ''
while (chars.length && chars[0].match(/\w/))
str += chars.shift()
return {value: str}
}
// "runtime"
ops = {
'+': (a, b) => a + b,
'-': (a, b) => a - b,
}
function evaluate(node, bindings) {
if (node.value)
return bindings[node.value]
return eval_op(
node.op,
evaluate(node.left, bindings),
evaluate(node.right, bindings))
}
function eval_op(op, left, right) {
let fn = ops[op]
return left.map((_, i) => fn(left[i], right[i]))
}
// demo
input = 'abc+def+xyz'
bindings = {
'abc': [1, 2, 3],
'def': [4, 5, 6],
'xyz': [7, 8, 9],
}
ast = parse(input)
console.log('AST', ast)
res = evaluate(ast, bindings)
console.log('RESULT', res)
Of course, there's a lot more work involved to turn this sketch into real code. I'd suggest you learn a bit about formal grammars, parsers and parser generators.
Loop over the array indexes, and replace the placeholders with the current index in the corresponding element of map. Then call eval().
let len = Object.values(json.formula)[0].length;
for (let i = 0; i < len; i++) {
let formula = json.formula.replace(/{{([-\w]+)}}/g, (m, placeholder) => map[placeholder][i];
console.log(eval(formula));
}
I played around with this a while today and got a version working that does not require defining a formal AST, and only uses eval() to instantiate an arrow function that calculates each index of the output based on the input lists and an incrementing input index.
'use strict';
const json={
formula: '{{a45bc2a1-ed82-4ccd-a455-f7959e875aad}}+({{f6c2ef2b-a4fa-4cfb-b62d-d0d7c3e266d9}}*{{335563ad-a715-47b9-8e54-2b8553768168}})'
}
const map = {
'a45bc2a1-ed82-4ccd-a455-f7959e875aad': [1, 2, 3, 4, 5],
'f6c2ef2b-a4fa-4cfb-b62d-d0d7c3e266d9': [10, 20, 30, 40, 50],
'335563ad-a715-47b9-8e54-2b8553768168': [1, 2, 3, 4, 5]
}
function parseFormula (formula) {
const template = /{{([^}]*)}}/;
const idList = [];
for (let i = 0; template.test(formula); i++) {
idList.push(formula.match(template)[0].replace(/[{}]/g, ''));
formula = formula.replace(template, `input[${i}][i]`);
}
return {idList, formula};
}
function calculateFormula(map, parsed) {
const input = [];
const result = [];
const lambda = eval(`(input, i) => ${parsed.formula}`)
parsed.idList.forEach(id => input.push(map[id]));
for (let i = 0; i < input[0].length; i++) {
result.push(lambda(input, i));
}
return result;
}
const parsed = parseFormula(json.formula);
console.log(parsed);
console.log(calculateFormula(map, parsed));
Output:
{
idList: [
'a45bc2a1-ed82-4ccd-a455-f7959e875aad',
'f6c2ef2b-a4fa-4cfb-b62d-d0d7c3e266d9',
'335563ad-a715-47b9-8e54-2b8553768168'
],
formula: 'input[0][i]+(input[1][i]*input[2][i])'
}
[ 11, 42, 93, 164, 255 ]