Skip to content

Instantly share code, notes, and snippets.

@hotrungnhan
Last active March 24, 2024 10:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hotrungnhan/51209cbd5e7d6008673cc19f51ab669f to your computer and use it in GitHub Desktop.
Save hotrungnhan/51209cbd5e7d6008673cc19f51ab669f to your computer and use it in GitHub Desktop.
Simple JSON Math evaluate
// 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)
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