一点上下文,当我试图解决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。我不明白在这种情况下&
的数学函数是什么。
答案 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
的值从1
到2^Length_Of_Set
循环,在我们的示例中为2^3 = 8
。
以下是1
到8
的数字的二进制值:
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)
因此,再次回到关于子集的原始帖子 - 第一个周期产生0
和1
的所有可能组合,其固定长度为n
,等于元素数量在我们的集合中。
第二个循环产生2^k
个值,等于:
001
010
100
...
其中k
是原始集合中元素的索引。
这个2^k
与第一个周期产生的组合相乘,使用&
或AND
操作,给出了一个简单的结果,说明索引为k
的元素应该包括在最后的子集中。
答案 4 :(得分:1)
在二进制中,任何2的幂都只是一个1后跟零。简言之:
等等。
此外,如您所知,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时才为真。