Last active
March 24, 2024 10:01
-
-
Save hotrungnhan/51209cbd5e7d6008673cc19f51ab669f to your computer and use it in GitHub Desktop.
Simple JSON Math evaluate
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// we can store the formular in database in json format, and execute compute an value when ever we need it. | |
type OP = { | |
op: string | |
left?: number | OP | string | |
right?: number | OP | string | |
} | |
type OPEval = { | |
left?: number | |
right?: number | |
} | |
type OpToString = { | |
left: string | |
right: string | |
} | |
type OPHandle = (op: OPEval) => number | |
type OpToStringHandle = (op: OpToString) => string | |
const OPERAND_STORE = { | |
_repo: { | |
} as { [key in string]: { | |
precended_level: number, | |
handler: OPHandle, | |
toString: OpToStringHandle | |
} }, | |
register: function (op_name: string, handler: OPHandle, toString: OpToStringHandle, level: number = 0) { | |
this._repo[op_name] = { | |
precended_level: level, | |
handler: handler, | |
toString: toString | |
} | |
}, | |
toString: function (value: OP | number | string | undefined): string { | |
// improved it by using stack to check if () is needed =)) | |
if (value == undefined) { | |
return "" | |
} | |
if (typeof (value) == "string") { | |
return value; | |
} | |
if (typeof (value) == "number") { | |
return value.toString(); | |
} | |
const leftS = this.toString(value.left); | |
const rightS = this.toString(value.left); | |
const s = this._repo[value.op].toString({ | |
left: leftS, | |
right: rightS, | |
}); | |
return s; | |
}, | |
evaluate: function (value?: number | OP | string, vars?: { [key in string]: number }) { | |
if (typeof (value) == "number") { | |
return value | |
} else if (typeof (value) == "object") { | |
return this._eval(value, vars); | |
} else if (typeof (value) == "string") { | |
if (!vars) { | |
throw `vars not povided`; | |
} | |
const v = vars[value]; | |
if (!v) { | |
throw `variable : ${value} not povided`; | |
} | |
return v; | |
} | |
return undefined | |
}, | |
_eval: function (phrase: OP, vars?: { [key in string]: number }): number { | |
const leftValue = this.evaluate(phrase.left, vars) | |
const rightValue = this.evaluate(phrase.right, vars) | |
const evalHandler = this._repo[phrase.op].handler; | |
if (!evalHandler) { | |
throw `${phrase.op} not register !!!` | |
} | |
return evalHandler({ | |
left: leftValue, | |
right: rightValue, | |
}); | |
} | |
} | |
OPERAND_STORE.register("+", | |
(op) => { return (op.left || 0) + (op.right || 0) }, | |
(op) => { return op.left + "+" + op.right }) | |
OPERAND_STORE.register("-", | |
(op) => { return (op.left || 0) - (op.right || 0) }, | |
(op) => { return op.left + "-" + op.right }) | |
OPERAND_STORE.register("*", | |
(op) => { return (op.left || 0) * (op.right || 0) }, | |
(op) => { return op.left + "*" + op.right }) | |
OPERAND_STORE.register("/", | |
(op) => { | |
if (op.right == 0 || op.right == undefined) { | |
throw "Arithmetic error: can't devide by 0" | |
} | |
return (op.left || 0) / (op.right) | |
}, | |
(op) => { return op.left + "/" + op.right } | |
) | |
OPERAND_STORE.register("**", (op) => { | |
return (op.left || 0) ** (op.right || 0) | |
}, (op) => { return op.left + "**" + op.right }) | |
OPERAND_STORE.register("log", | |
(op) => { | |
if (!op.right) { | |
throw "log operator need right op" | |
} | |
if (!op.left) { | |
throw "log operator need left op" | |
} | |
return Math.log(op.right) / Math.log(op.left) | |
}, | |
(op) => { return `log_(${op.left})(${op.right})` }) | |
OPERAND_STORE.register("sin", | |
(op) => { | |
if (op.left) { | |
console.warn(`${op.left} is not require for sin(x)`) | |
} | |
return Math.sin(op.right || 0); | |
}, | |
(op) => { return `sin(${op.right || 0})` }) | |
OPERAND_STORE.register("cos", | |
(op) => { | |
if (op.left) { | |
console.warn(`${op.left} is not require for cos(x)`) | |
} | |
return Math.cos(op.right || 0); | |
}, | |
(op) => { return `cos(${op.right || 0})` }) | |
const vars = { | |
"$a": 2.1 | |
} | |
// 1+(5* log_(sin(1))($a)) | |
const formular: OP = { | |
op: "+", | |
left: 1, | |
right: { | |
op: "*", | |
left: { | |
op: "*", | |
right: 5, | |
left: { | |
op: "/", | |
left: 1, | |
right: 2 | |
} | |
}, | |
right: { | |
op: "log", | |
left: { | |
op: "sin", | |
right: 1, | |
}, | |
right: "$a", | |
}, | |
} | |
} | |
const result = OPERAND_STORE.evaluate(formular, vars) | |
const str = OPERAND_STORE.toString(formular) | |
console.log(result) | |
console.log(str) | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
interface OPAbtract { | |
readonly op: string, | |
readonly precedence: number, | |
eval: (context: EvalContext) => number, | |
toString: (context: EvalContext) => string | |
} | |
type Operand = number | OPAbtract | string | |
class EvalContext { | |
private readonly vars: { [key in string]: number } | |
static create(vars: { [key in string]: number }): EvalContext { | |
return new EvalContext(vars) | |
} | |
private constructor(vars: { [key in string]: number }) { | |
this.vars = vars; | |
} | |
toString(op: Operand, parentPrecedence?: number): string { | |
if (typeof (op) == "number") { | |
return op.toString(); | |
} else if (typeof (op) == "string") { | |
return op; | |
} | |
if (parentPrecedence && op instanceof Binary && parentPrecedence > op.precedence) { | |
return "(" + op.toString(this) + ")"; | |
} | |
return op.toString(this) | |
} | |
computeValue(op: Operand): number { | |
if (typeof (op) == "number") { | |
return op; | |
} else if (typeof (op) == "string") { | |
const value = this.vars[op] | |
if (!value) { | |
throw "Invalid Variable" | |
} | |
return value; | |
} | |
return op.eval(this); | |
} | |
} | |
abstract class Unary<U extends Operand> implements OPAbtract { | |
readonly op: string; | |
readonly precedence: number; | |
value: U | |
constructor(op: string, value: U, precedence: number = 0) { | |
this.precedence = precedence | |
this.op = op; | |
this.value = value; | |
} | |
eval(context: EvalContext): number { | |
throw new Error("Method not implemented."); | |
} | |
toString(context: EvalContext): string { | |
throw new Error("Method not implemented."); | |
} | |
} | |
abstract class Binary<L extends Operand, R extends Operand> implements OPAbtract { | |
readonly op: string; | |
readonly precedence: number; | |
left: L | |
right: R | |
constructor(op: string, left: L, right: R, precedence: number = 0) { | |
this.precedence = precedence | |
this.op = op; | |
this.left = left; | |
this.right = right; | |
} | |
eval(_: EvalContext): number { | |
throw new Error("Method not implemented."); | |
} | |
toString(context: EvalContext) { | |
const leftV = context.toString(this.left, this.precedence); | |
const rightV = context.toString(this.right, this.precedence); | |
return `${leftV} ${this.op} ${rightV}` | |
} | |
} | |
class Plus<L extends Operand, R extends Operand> extends Binary<L, R> { | |
constructor(left: L, right: R) { | |
super("+", left, right) | |
} | |
eval(context: EvalContext): number { | |
const leftV = context.computeValue(this.left); | |
const rightV = context.computeValue(this.right); | |
return leftV + rightV | |
} | |
} | |
class Subtract<L extends Operand, R extends Operand> extends Binary<L, R> { | |
constructor(left: L, right: R) { | |
super("-", left, right) | |
} | |
eval(context: EvalContext): number { | |
const leftV = context.computeValue(this.left); | |
const rightV = context.computeValue(this.right); | |
return leftV - rightV | |
} | |
} | |
class Multiply<L extends Operand, R extends Operand> extends Binary<L, R> { | |
constructor(left: L, right: R) { | |
super("*", left, right, 5) | |
} | |
eval(context: EvalContext): number { | |
const leftV = context.computeValue(this.left); | |
const rightV = context.computeValue(this.right); | |
return leftV * rightV | |
} | |
} | |
class Divide<L extends Operand, R extends Operand> extends Binary<L, R> { | |
constructor(left: L, right: R) { | |
super("/", left, right, 5) | |
} | |
eval(context: EvalContext): number { | |
const leftV = context.computeValue(this.left); | |
const rightV = context.computeValue(this.right); | |
return leftV / rightV | |
} | |
} | |
class Pow<Base extends Operand, Exponent extends Operand> extends Binary<Base, Exponent> { | |
constructor(base: Base, exponent: Exponent) { | |
super("^", base, exponent, 10) | |
} | |
eval(context: EvalContext): number { | |
const base = context.computeValue(this.left); | |
const exponent = context.computeValue(this.right); | |
return Math.pow(base, exponent) | |
} | |
} | |
class Cos<U extends Operand> extends Unary<U> { | |
constructor(angel: U) { | |
super("cos", angel) | |
} | |
eval(context: EvalContext): number { | |
const v = context.computeValue(this.value); | |
return Math.cos(v); | |
} | |
toString(context: EvalContext) { | |
const v = context.toString(this.value); | |
return `cos(${v})` | |
} | |
} | |
class Sin<U extends Operand> extends Unary<U> { | |
constructor(angel: U) { | |
super("sin", angel) | |
} | |
eval(context: EvalContext): number { | |
const v = context.computeValue(this.value); | |
return Math.sin(v); | |
} | |
toString(context: EvalContext) { | |
const v = context.toString(this.value); | |
return `sin(${v})` | |
} | |
} | |
class Log<U extends Operand, V extends Operand> extends Binary<U, V> { | |
constructor(base: U, value: V) { | |
super("sin", base, value) | |
} | |
eval(context: EvalContext): number { | |
const base = context.computeValue(this.left); | |
const value = context.computeValue(this.right); | |
return Math.log(value) / Math.log(base); | |
} | |
toString(context: EvalContext) { | |
const base = context.toString(this.left); | |
const value = context.toString(this.right); | |
return `log_(${base})(${value})` | |
} | |
} | |
const formular: OPAbtract = new Pow( | |
new Plus(5, 4), | |
new Multiply(2, new Pow("nhan", 2)) | |
) | |
const context = EvalContext.create({ "nhan": 2 }) | |
const result = formular.eval(context); | |
console.log(result, formular.toString(context)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment