模2运算及模2运算式计算 (js)

概述

学习二维码生成的时候卡在纠错码部分,然后就接触到了伽罗华域,了解到模2运算,恰好前不久刚了解了波兰表达式,就尝试写一个支持模2运算的算式解析计算。

结果

'10011*101+101'.M2Calc() =>"1011010"

涉及内容

  • 模2运算
  • 波兰表达式(运算式解析)

实现

没有经过大量检测,如有问题请指正。
部分解读为个人理解,表述不会太标准。

异或xor(a,b)

mod2 加,减,乘,除都用到异或操作,js中的异或符^是将两侧内容看作10进值然后转成2进值异或,如下

'1001100' ^ '10011'  //1008023  错误
(parseInt('1001100',2)^parseInt('10011',2)).toString(2) //"1011111" 正确

因此不能直接使用此符进行异或,需要先转成十进值,运算完成后在再转回二进制字符串;或者也可以自定义二进制异或处理方式。
将异或作为公共方法

/**
 * 二进制字符串异或
 * @param bs0
 * @param bs1
 * @param type
 * @returns {string}
 */
function xor(bs0, bs1, type = 1, resultLen) {
  //头部去0
  bs0 = bs0.replace(/^0+/, '');
  bs1 = bs1.replace(/^0+/, '');
  //多种方式进行异或
  //1) 异或符
  let result;
  if (type == 1) {
    result = (parseInt(bs0, 2) ^ parseInt(bs1, 2)).toString(2);
  }
  //2)自定义二进制字符串异或
  if (type == 2) {
    let maxLen = Math.max(bs0.length, bs1.length);
    bs0 = bs0.padStart(maxLen, '0');
    bs1 = bs1.padStart(maxLen, '0');

    let temp = '';
    for (let i = 0; (bs0[i] != null || bs1[i] != null); i++) {
      temp += (bs0[i] || 0) ^ (bs1[i] || 0)
    }
    result = temp.replace(/^0+/, '');
  }
  return resultLen ? result.padStart(resultLen, '0') : result
}

加法 M2Add(a,b)

mod2 加法定义为0+0=0 ;0+1=1 ; 1+0=1 ; 1+1=0;实际上就是二进制的异或

/**
 * 模2加法 等于异或
 * 
 * @param bs0
 * @param bs1
 * @returns {string}
 */
function M2Add(bs0, bs1) {
  return xor(bs0, bs1,1)
}

减法 M2Sub(a,b)

mod2 减法定义为 0-0=0 ; 0-1=1; 1-0=1; 1-1=0;实际上也是二进制的异或

/**
 * 模2减法  等于 异或
 * 
 * @param bs0
 * @param bs1
 * @returns {string}
 */
function M2Sub(bs0, bs1) {
  return xor(bs0, bs1,1)
}

乘法M2Pow(a,b)

M2Pow('10011','101') =>"1011111"

    10011
X     101
---------
    10011  组1 (这里标注'组'与下方乘法函数中的注释对应)
  100112
---------
  1011111
/**
 * 模2乘法
 * @param bs0 乘数  头部去零
 * @param bs1 乘数 头部去零
 * @returns {string}  如果输入的乘数头部不为零,则输出结果头部也不为零
 */
function M2Pow(bs0, bs1) {
  let arr = [];//保存每一组乘数结果
  for (let i = 0; i < bs1.length; i++) {
    if (bs1[i] == '1') {
      arr.push(bs0 + ''.padStart(i, '0')) //补零
    }
  }
  //累计求和,不直接调用模2加,采用更直接的方式
  return arr.reduce((a, b) => xor(a, b,1), '0');
}

除法(取商) M2Div(a,b)

M2Div('1011010','10011') => "101"

/**
 * 模2 取商运算
 * @param bs0
 * @param bs1
 * @returns {string}
 */
function M2Div(bs0, bs1) {
  //TODO 可以使用查表优化的,但是要分情况,所以暂时不用
  let fastDict = {
    '11': '0101',
    '10': '0110',
    '01': '0010',
    '00': '0000',
  };
  //头部去0操作
  bs0 = bs0.replace(/^0+/, '');
  bs1 = bs1.replace(/^0+/, '');

  let bs1_len = bs1.length;

  //被除数根据除数的长度分为两部分,第一部分长度为除数长度
  let p0 = bs0.substr(0, bs1_len);
  let p1 = bs0.substr(bs1_len).split('');

  //每次相除只要余数长度(头部去零之后的长度)大于等于除数,就可以继续进行下一轮运算
  let quo = '';
  while (p0.length >= bs1_len) {
    //第一部分的首位为0,则与0 模2减,否则与除数模2减
    quo += p0[0];
    p0 = xor(p0, p0[0] == '0' ? '0': bs1,1,bs1_len);
    //首位除0
    p0 = p0.replace(/^0/, '');
    //从p1头部获取一位
    if (p1.length) {
      p0 += p1.shift()
    }
  }
  return quo.replace(/^0+/, '');
}

取模M2Mod(a,b)

其实是取商运算的简化,不用对商做维护;
M2Mod('1011010','10011') => "101"

             101
        ---------
 10011 丿1011010
         10011
         ------
          01011
          00000
          -----
           10110
           10011
           ------
             101
/**
 * 模2 取余运算,没有余数返回 ''
 * @param bs0 被除数
 * @param bs1 除数
 * @returns {string}
 */
function M2Mod(bs0, bs1) {
  let fastDict = {
    '11': '0101',
    '10': '0110',
    '01': '0010',
    '00': '0000',
  };
  //头部去0操作
  bs0 = bs0.replace(/^0+/, '');
  bs1 = bs1.replace(/^0+/, '');

  let bs1_len = bs1.length;

  //被除数根据除数的长度分为两部分,第一部分长度为除数长度
  let p0 = bs0.substr(0, bs1_len);
  let p1 = bs0.substr(bs1_len).split('');

  //每次相除只要余数长度(头部去零之后的长度)大于等于除数,就可以继续进行下一轮除法
  while (p0.length >= bs1_len) {
    //第一部分的首位为0,则与0 模2减,否则与除数模2减
    p0 = xor(p0, p0[0] == '0' ? '0': bs1,1,bs1_len);
    //首位除0
    p0 = p0.replace(/^0/, '');
    //从p1头部获取一位
    if (p1.length) {
      p0 += p1.shift()
    }
  }
  return p0.replace(/^0+/, '');
}

运算式M2Calc(str)

使用逆波兰表达式计算

'10011*101+101'.M2Calc() =>"1011010"


/**
 *判断是 运算符|操作数|括号
 *@return 1:操作数 2:括号 3:运算符
 */
function typeChk(d) {
  return /[01]+/.test(d) ? 1 : (/[\(\)]/.test(d) ? 2 : 0) //操作数返回1
}

/**
 * 处理输入
 * 1) 替换中英括号
 * 2) 去除空格
 * 4) 返回字符分割
 */
function dealStr(str) {
  return str
    .replace(//, '(')
    .replace(//, ')')
    .replace(/[^01\(\)\+\-\*\/%]/g, '')
    .match(/([01]+)|\(|\)|\+|\-|\*|\/|%/g)
}

/**
 * 模2算式计算  + - * /(取商) %(取模)
 * 使用逆波兰表达式
 * @param str
 */
function M2Calc(str) {
  //一般的算数表达式即为中缀表达式
  str = str || "10011 * 101";//这里可以不用加默认组织
  //运算符权重
  let sdw = {'+': 1, '-': 1, '*': 2, '/': 2, '%': 2};
  //运算符转模2计算
  let fnDict = {
    '+': M2Add, '-': M2Sub, '*': M2Pow, '/': M2Div, '%': M2Mod
  };
  //循环安全数
  let saveCircle = 50;

  let operand = [];//操作数栈
  let operator = [];//操作符栈

  //去空格(主要是避免数字被空格拆分),分割成数组
  let arr = dealStr(str);
  //按照规则入栈
  let type;
  for (let i = 0, curr; (curr = arr[i]) != null; i++) {
    type = typeChk(curr);
    if (type == 1) {
      operand.push(+curr)
    } else if (type == 0) {
      let si = 0, stackTop;
      while (true && si++ < saveCircle) {
        stackTop = operator[operator.length - 1];
        if (stackTop == null || stackTop == '(') {
          operator.push(curr);
          break;
        } else if (sdw[curr] > sdw[stackTop]) {
          operator.push(curr);
          break;
        } else {
          operand.push(operator.pop())
        }
      }
    } else {
      if (curr == '(') {
        operator.push(curr)
      } else {
        let s, si = 0;
        while (s != '(' && si++ < saveCircle) {
          s = operator.pop();
          if (s == '(') {
            break;
          } else {
            operand.push(s)
          }
        }
      }
    }
  }
  //将操作符栈中剩下的弹出压入操作数栈
  let prevStack = [...operator, ...operand.reverse()];

  //计算
  let resultStack = [], left, right;
  for (let i = prevStack.length - 1, curr; (curr = prevStack[i]) != null; i--) {
    if (typeChk(curr)) {
      //是操作数
      resultStack.push(curr)
    } else {
      //是操作符  注意这里right/left 和后缀是相反地,先出的是右
      right = resultStack.pop();
      left = resultStack.pop();
      resultStack.push(fnDict[curr](left + '', right + ''))
    }
  }
  // console.log(prevStack);
  // console.log(resultStack);
  return resultStack.length != 1 ? '表达式有误' : resultStack[0]
}
//绑定到字符串
String.prototype.M2Calc = function(){
  return M2Calc(this)
};

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值