按位AND(&)运算符做什么数学函数(JS)?

时间:2017-12-13 21:04:43

标签: javascript bitwise-operators

一点上下文,当我试图解决javascript问题以查找所有可能的子集时,我正在查看另一个SO帖子。我不是在询问JS挑战,而是为什么它存在,它有什么数学意义?

以下是this SO post

代码的复制粘贴
var arr = [1, 2, 3];

function generatePowerSet(array) {
  var result = [];
  result.push([]);

  for (var i = 1; i < Math.pow(2, array.length); i++, result.push(subset))
    for (var j = 0, subset = []; j < array.length; j++)
      if (i & Math.pow(2, j))
        subset.push(array[j]);

  return result;
}

console.log(generatePowerSet(arr));

我不明白if (i & Math.pow(2, j))行正在完成的工作。 mozilla doc表示它对每个位对进行AND比较。为什么相关?

当我说相关时,例如使用LEFT SHIFT,执行&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt; 1乘以a乘以2。如果a << b且b为1,则其等效数学函数乘以2。我不明白在这种情况下&的数学函数是什么。

5 个答案:

答案 0 :(得分:3)

i 中的j th 位为1时,表达式i & Math.pow(2, j)给出非零值(从最低有效位计数,即0 th 位。

这可以通过示例得到最好的解释。假设 i 在某个时刻是10;在二进制:1010。现在让 j 为0.然后:

     i & Math.pow(2, j)
 ==  10 & Math.pow(2, 0)
 ==  10 & 1
 ==  0b1010 & 0b0001
 ==  0b0000

第二个值(0b0001)的效果是过滤:它从第一个值中精确地过滤掉一个比特。看看 j 为1时会发生什么:

     i & Math.pow(2, j)
 ==  10 & Math.pow(2, 1)
 ==  10 & 2
 ==  0b1010 & 0b0010
 ==  0b0010

因此 j 的值if条件为真。

由于 i 有两个1位,对于 i 的特定值,if条件为真两次。

答案 1 :(得分:3)

让我们看一个递增1的按位计数器:

 0 -> 000
 1 -> 001
 2 -> 010
 3 -> 011
 4 -> 100
 ...
 7 -> 111

正如您所看到的,如果我们想象1为真,0为假,它实际上会生成所有可能的4个布尔组合。所以它实际上很适合生成powerSet。我们只需要一个计数器(i)超过这些值,然后我们需要将布尔值转换为数组元素。为此,我们需要从左到右检查它

 for (var j = 0, subset = []; j < array.length; j++)

并检查是否检查了一个布尔值(位)

if (i & Math.pow(2, j))

如果是这样,我们将该数组索引的元素包含在结果中。

答案 2 :(得分:1)

&是一种按位AND比较。它做了什么比较两个数字的二进制表示,逐位。

在您的示例中,1,2和3以二进制形式表示为01,10和11.因此,1 & 2 = 01 & 10 = 0。同样,1 & 3 = 01 & 11 = 01 = 1

if (i & Math.pow(2, j))行正在对i & 2^j进行此比较。

答案 3 :(得分:1)

那篇文章是关于生成集合的所有子集。作为示例[1, 2, 3]设置。

现在让我们查看您拥有的代码:

for (var i = 1; i < Math.pow(2, array.length); i++, result.push(subset))

i的值从12^Length_Of_Set循环,在我们的示例中为2^3 = 8

以下是18的数字的二进制值:

1 = 001
2 = 010
3 = 011
4 = 100
5 = 101
6 = 110
7 = 111

如果我们说,那些3位中的每一位代表我们原始集合中的值,如果它是1,那么我们将其包含在子集中。这意味着上面的7二进制值代表了我们原始集合的所有可能子集。

两个数字之间的逻辑AND运算导致二进制表示之间的按位乘法运算:

1 & 2^0 = 001 * 001 = 001 (0*0, 0*0, 1*1)
2 & 2^0 = 010 * 001 = 000 (0*0, 1*0, 0*1)
...
7 & 2^2 = 111 * 100 = 100 (1*1, 1*0, 1*0)

因此,再次回到关于子集的原始帖子 - 第一个周期产生01的所有可能组合,其固定长度为n,等于元素数量在我们的集合中。

第二个循环产生2^k个值,等于:

001
010
100
...

其中k是原始集合中元素的索引。

这个2^k与第一个周期产生的组合相乘,使用&AND操作,给出了一个简单的结果,说明索引为k的元素应该包括在最后的子集中。

答案 4 :(得分:1)

在二进制中,任何2的幂都只是一个1后跟零。简言之:

  • 2 ^ 0:1
  • 2 ^ 1:10
  • 2 ^ 2:100

等等。

此外,如您所知,A的幂集是A的所有子集的集合。方便地,如果A具有n个元素,则A的子集可以由n个比特表示,其中每个比特对应于一个元素。如果该位为1,我们包括该元素,如果不是,我们不会。

因此,要获得功率集,如果我们可以通过1到2 ^ n的每个数字,并相应地创建子集。所以我们只需要一种方法从我们给出的数字生成子集。

所以行:

// get each number from 1 -> 2^n. These represent each subset.
for (var i = 1; i < Math.pow(2, array.length); i++, result.push(subset))
  // Now we need to see which elements of the original array
  // go into this subset. So we check each one individually:
  for (var j = 0, subset = []; j < array.length; j++)
    // And finally, we only put this item in the subset if
    // the bit at index we are at is 1.
    if (i & Math.pow(2, j))
      subset.push(array[j]);

关键点是,如果同一索引处的位为1 ,我们应该仅包含该元素。所以,为此,我们使用该按位运算符。现在,我们知道在二进制中,除了在第j个位置的单个1之外,2 ^ j全为零。那么如果你用i本身按位和那个,如果i的第j个位置的位是0(假),或者2 ^ n(truthy),结果将全为0否则。

所以TL; DR:

(i & Math.pow(2, j))

当且仅当数字j的二进制表示的i第#位为1时才为真。