按位与(&)运算符的数学函数是什么(JS)?

What mathematical function does bitwise AND (&) operator do (JS)?

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

这是从

复制粘贴的代码
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,将 << 1 乘以 2。如果 a << b 且 b 为 1,则其等效数学函数乘以二。我不明白数学函数 & 在这种情况下的作用。

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

这可以通过示例得到最好的解释。假设 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

if 条件因此对于 j 的值成立。

由于 i 有两个 1 位,因此对于 i[= 的特定值,if 条件将有两次为真35=].

让我们看看一个递增 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))

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

& 是按位 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 进行比较。

post 是关于生成集合的所有子集。以[1, 2, 3]集为例。

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

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

i 的值从 1 循环到 2^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)

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

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

001
010
100
...

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

而这个 2^k,乘以第一个循环产生的组合,使用 &AND 操作,给我们一个简单的结果,如果索引 [=29 处的元素=] 应包含在最终子集中。

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

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

等等。

另外,如你所知,A的幂集是A的所有子集的集合。方便地,如果A有n个元素,那么A的一个子集可以用n位表示,其中每一位对应A 的一个元素。如果该位是 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(假),则结果将全为 0,否则为 2^n(真) .

所以 TL;DR:

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

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