使用位运算增删状态

161 阅读4分钟

位运算简介

位运算符有&|,用于数字间的运算,运算时将两个数字转为二进制,然后对每个同位进行运算得出01,最后将结果拼在一起得到最终结果

比如4 & 3,运算方式为:0b100 & 0b011 = 0b000,结果为0

一位表示有一种水果

用二进制数中的一位表示拥有一种水果,以四位为例,从左到右四位分别表示榴莲(durian)樱桃(cherry)香蕉(banana)苹果(apple),有为1,没有为0,所以0b1111表示四种水果都有的状态,一个水果都没有的状态则用0b0000来表示

假装有人要去买水果

下面是一道题目:

假设有一位顾客小李要去买苹果香蕉樱桃与,那么可以用数字0b0111来表示购买水果的种类。

假设小李的老婆也去买水果,她买的是苹果香蕉榴莲,那么可以用数字0b1011来表示她购买水果的种类。

请求出小李和她老婆都买了哪个水果?

解答

求两者的并集可以用&运算符来计算,0b0111 & 0b1011的结果0b0011,作为人类,我们可以一眼就得出0b0011中有苹果香蕉两种水果,那在程序中如何通过位运算来计算呢

将有某一种水果的状态用一个二进制数来表示,代码如下:

const apple = 0b0001;
const banana = 0b0010;
const cherry = 0b0100;
const durian = 0b1000;

两者都买的水果的值为0b0011,将这个值与有某种水果的二进制数值进行&位运算,如果结果为0b0000则表示没有,只要结果不为0b0000即表示有,最终代码如下:

const apple = 0b0001;
const banana = 0b0010;
const cherry = 0b0100;
const durian = 0b1000;

// 水果二进制值到名称
const fruitBinaryToName = {
  [apple]: "苹果",
  [banana]: "香蕉",
  [cherry]: "樱桃",
  [durian]: "榴莲",
};

const fruits = [apple, banana, cherry, durian];

// 小李买了苹果、香蕉和樱桃
const fruitsBuiedByLi = 0b0111;

// 小李妻子买了苹果、香蕉和榴莲
const fruitsBuiedByWife = 0b1011;

// 两者都买的水果
const fruitsBothBuied = fruitsBuiedByLi & fruitsBuiedByWife;

// 两者都买水果的名称
const namesBothBuied = [];

for (const fruit of fruits) {
  // 如果求值不为0则表示有这种水果
  if ((fruitsBothBuied & fruit) !== 0) {
    namesBothBuied.push(fruitBinaryToName[fruit]);
  }
}
 
console.log("两者都买的水果:", namesBothBuied);

分析

在上面这道题中,首先运用了位运算来求交集,即得出小李与妻子都买的水果。

之后又用位运算来判断是否有某种状态,即使用都买的水果每个水果进行&运算,值不为0则表示有

上述题中如果要计算小李与妻子买的全部水果种类(并集),则可以用|运算来得出结果

添加与删除

使用上述所说方式还可以实现添加、删除状态(水果)

假设小李在结算的时候突然不要香蕉了,改为要买榴莲,那要如何计算小李最终购买水果的二进制值呢?

删除

不要香蕉,即要在小李所买水果值中删除香蕉,也就是0b0111中移除0b0010

计算方式为:先将香蕉的值0b0010取反得到0b1101,再与小李所买水果值0b0111进行&运算,得到的结果为0b0101,这个值表示苹果樱桃,也就移除了香蕉,代码如下:

const apple = 0b0001;
const banana = 0b0010;
const cherry = 0b0100;
const durian = 0b1000;

// 小李买了苹果、香蕉和樱桃
const fruitsBuiedByLi = 0b0111;

// 小李所买水果中移除香蕉
const noBananaFruit = fruitsBuiedByLi & ~banana;

console.log(noBananaFruit.toString(2)); // 101

添加

计算添加榴莲则比较简单,只需要将榴莲并进来即可,并操作使用|运算符,即0b0101 | 0b1000,结果为0b1101,代码如下(接上面的代码):

// ... 省略删除中的代码

// 小李所买水果中添加榴莲
const addDurianFruit = noBananaFruit | durian;
console.log(addDurianFruit.toString(2)); // 1101

总结

使用二进制数的每一位来表示一种状态,则可以使用位运算对状态进行添加、删除,并可以判断是否含有某种状态。这种方式尤其适合计算量比较大的操作中,比如React源码中便采用了这种方式来标记需要对元素进行何种操作。