counter

104 阅读2分钟

栈计算器

加减乘除 maths.js

// 加
function add(a, b) {
    return a + b;
}
// 减
function reduce(a, b) {
    return a - b;
}
// 乘
function rede(a, b) {
    return a * b;
}
// 除
function except(a, b) {
    return a / b;
}

字符匹配 tools.js

// 字符去空格
// str : 被处理字符 
// isGlobal : false 前后去空
// isGlobal : true  完全去空
function trim(str, isGlobal = false) {
    var result;
    result = str.replace(/(^\s+)|(\s+$)/g, ""); 
    if (isGlobal) {
        result = result.replace(/\s/g, "");
    }
    return result;
}
// 小括号匹配规则
// 小括号成对出现 且 以左括号为开始(非开头字符)
function dbBrackets(str) {
    const strArr = str.split("");
    const breacketNum = strArr.reduce((acc, next) => {
        if (next === "(") {
            acc += 1;
        }
        if (next === ")") {
            acc -= 1;
        }
        if (acc === -1) {
            acc = NaN;
        }
        return acc;
    }, 0);
    return breacketNum === 0 ? str : false;
}
// 是否为数字 
function isNum(num) {
    return typeof num === "number" && isFinite(num);
}
// 是否为数字字符  
function isNumStr(str) {
    return str.search(/^([1-9]\d*|0)(\.\d+)?$/) !== -1;
}

栈 Stack.js

// 栈
class Stack {
    constructor() {
        this.store = [];
    }
    // 栈顶元素
    top() {
        const item = this.out();
        this.in(item);
        return item;
    }
    // 反转栈
    // save = true  反转
    // save = false 不反转
    reverse(save = false) {
        let cache = [...this.store].reverse();

        if (save) {
            this.store = cache;
        }

        return cache;
    }
    // 入栈
    in(data) {
        this.store.push(data);
    }
    // 出栈(栈尾),并返回该元素。
    out() {
        return this.store.pop();
    }
    // 出栈(栈顶),并返回该元素。
    reOut() {
        return this.store.shift();
    }
    // 空  : true
    // 非空: false
    isNull() {
        return !this.store.length;
    }
    // 栈高
    len() {
        return this.store.length;
    }
}
export default { Stack };

计算器 counter.js

import { Stack } from "./Stack";
import { add, reduce, rede, except } from "./maths"; 
import { trim, dbBrackets, isNum, isNumStr } from "./tools";

/**
 * @name  栈计算器
 * @param {function} errFn  计算式验证失败回调
 * @prop  {Map}      math   可使用计算方法
 * @prop  {stack}    stack  计算式拆解后的栈结构
 * @prop  {Array}    valids 验证方法
 */
class Counter {
    constructor(errFn) {
        this.math = new Map([]);
        this.stack = new Stack();
        this.valids = [dbBrackets, ...this.validDef()];
        this.errFn = errFn;
        this.setDefMath();
    }

    toError() {
        this.errFn && this.errFn();
    }

    // 默认计算方法
    setDefMath() {
        this.register("+", add, 1);
        this.register("-", reduce, 1);
        this.register("*", rede, 2);
        this.register("/", except, 2);
    }

    /**
     * 计算式验证
     * @param {function} valids
     * @returns {string | false} 验证通过返回 验证字符供下级函数使用, 失败 返回false 终止下级函数调用
     * @remind 本身默认验证未对复杂模式做 错误处理,例如 ++ --
     */
    addValid(valids) {
        if (Array.isArray(valids)) {
            this.valids = [...this.valids, ...valids];
        } else {
            this.valids.push(valids);
        }
    }

    // 验证调用
    valid(data) {
        return this.valids.reduce((acc, next) => {
            return acc === false ? false : next(acc);
        }, data);
    }

    /**
     * @name 计算方法注册
     * @summary 重复注册将覆盖
     * @param {string} symbol 计算符号 例如 +
     * @param {*} fn  计算调用方法 将获取两位数字作为参数
     * @param {*} weight  计算权重/优先级 例如  * > +
     */
    register(symbol, fn, weight) {
        if (typeof fn !== "function") {
            weight = fn;
            fn = null;
        } 
        this.math.set(symbol, {
            fn,
            weight,
        });
    }

    /**
     * @name 默认符号验证
     */
    validDef() {
        const _this = this; 
        function validSymbol(str) {
            const rxSymbol = _this.getRx(_this.getSymbol());
            const after = str.replace(rxSymbol, "");
            return !!!after ? str : false;
        } 
        // 非 数字 左括号开头
        function prefix(str) {
            const rxNum = /^\d|^\(/gim;
            return str.search(rxNum) !== -1 ? str : false;
        } 
        return [validSymbol, prefix];
    }

    /**
     * @name 获取所有 操作计算式符号
     */
    getSymbol() {
        return [...this.math.keys(), "(", ")"];
    }

    /**
     * @name 计算式符号正则
     * @summary 用来拆解计算式
     * @param {[]string} symbol
     */
    getRx(symbol) {
        let rule = symbol.reduce((acc, next) => {
            return `${acc}${acc === "" ? "" : "|"}\\${next}`;
        }, ""); 
        rule = `${rule}|([1-9]\\d*|0)(\\.\\d+)?`;
        return new RegExp(rule, "igm");
    }

    /**
     * @name 计算计算式结果
     * @param {Stack} stack 计算式拆解后的栈
     */
    getResult(stack) {
        let numStack = new Stack();
        let math = this.math; 
        stack = stack || this.stack; 
        while (!stack.isNull()) {
            // 反向出栈
            let top = stack.reOut(); 
            if (isNum(top)) {
                numStack.in(top);
            } else {
                let currentMath = math.get(top).fn; 
                // 减法有先后顺序
                let secentNum = numStack.out();
                let firstNum = numStack.out();
                numStack.in(currentMath(firstNum, secentNum));
            }
        } 
        return numStack.out();
    }

    /**
     * @name 计算式拆解入栈
     * @param {string} formula 计算式
     */
    resolver(formula) {
        formula = trim(formula); 
        if (!this.valid(formula)) {
            return this.toError();
        } 
        const math = this.math;
        const symbol = this.getSymbol();
        const rx = this.getRx(symbol);
        const codes = formula.match(rx);
        const symbols = new Stack();
        const stack = this.stack;

        codes.forEach((code) => {
            // 数字入栈
            if (isNumStr(code)) {
                stack.in(parseFloat(code));
            } else {
                // 空栈 | ( | 上级为 (
                if (symbols.isNull() || code === "(" || symbols.top() === "(") {
                    symbols.in(code);
                } else if (code == ")") {
                    let top = symbols.out(); 
                    while (top !== "(") {
                        stack.in(top);
                        top = symbols.out();
                    }
                } else {
                    let top = symbols.out();
                    let topWeight = math.get(top).weight;
                    let codeWeight = math.get(code).weight;

                    while (
                        top !== "(" &&
                        !symbols.isNull() &&
                        topWeight >= codeWeight
                    ) {
                        stack.in(top);

                        // 更新比较值, top可能为 ( 该符号未注册到方法库中
                        top = symbols.out();
                        if (top !== "(") {
                            topWeight = math.get(top).weight;
                        }
                    }

                    if (topWeight >= codeWeight) {
                        stack.in(top);
                    } else {
                        symbols.in(top);
                    }

                    symbols.in(code);
                }
            }
        }); 
        // 清空符号栈
        while (!symbols.isNull()) {
            stack.in(symbols.out());
        } 
        return stack;
    }
}
export default { Counter };