【问题标题】:Understanding subsets of array了解数组的子集
【发布时间】:2016-11-17 10:24:50
【问题描述】:

您可以在此处 (Find all possible subset combos in an array?) 看到,提出的一种解决方案是

var sets = (function(input, size){
    var results = [], result, mask, i, total = Math.pow(2, input.length);
    for(mask = size; mask < total; mask++){
        result = [];
        i = input.length - 1; 
        do{
            if( (mask & (1 << i)) !== 0){
            result.push(input[i]);
        }
        }while(i--);
        if( result.length >= size){
        results.push(result);
        }
    }   

return results; 
})(['a','b','c','d','e','f'], 2);
console.log(sets);

首先,我想知道确切的尺寸和面具在做什么。即为什么我们的函数需要第二个参数。我还对位运算符进行了一些研究,但仍然不确定我们在做什么以及为什么要这样做

mask & (1 << i)) !== 0

这意味着什么?

【问题讨论】:

  • 你为什么没有在提到的问题中问这个,作为对答案的评论?
  • 从 2013 年开始,由于文字较大,我必须写下来才能理解,所以一个新问题更合适。
  • mask &amp; (1 &lt;&lt; i) 检查掩码的第 i 位(解释为带符号的 32 位整数)是否设置为 1。

标签: javascript arrays


【解决方案1】:

size 参数是必需的,因为最初的问题是生成最小尺寸(2)的子集。该参数允许您指定最小尺寸。这很好,因为您可以使用相同的代码来获取最小大小为 3 的所有子集。

变量 mask 采用所有可以用 input.length 位生成的整数值。因此,以二进制表示,mask 采用以下值:

000000
000001
000010
000011
000100
... etc...
111100
111101
111110
111111

每 1 位表示输入数组中的相应元素应该是子集的一部分。所以上述 mask 的值代表以下子集:

[]
['a']
['b']
['a','b']
['c']
... etc ...
['c','d','e','f']
['a','c','d','e','f']
['b','c','d','e','f']
['a','b','c','d','e','f']

这有点令人困惑,因为我们用右边的最低有效位编写二进制表示,而左边的第 0th 元素显示数组。

从这些子数组中,只有具有最小 size 元素的那些会保留在结果中。所以在 size = 2 的情况下,不保留前 3 个:保留的第一个是 ['a','b']

出于这个原因,算法有所改进,并且 mask 不是从 0 开始的,因为它已经确定找不到足够大的子集 mask 更小或等于大小。这就是为什么for 循环以该值开始的原因。但这并不重要。它也可以从零开始:

for (mask = 0; mask < total; mask++)

然后关于if:它测试是否在 mask 的二进制表示中设置了某个位:因为单个位 (1) 向左移动(所以它变成了什么比如二进制表示的 10000),然后用 mask 进行 AND-ed(使用 &amp;)。例如,如果 mask 在某个时刻是 101001 并且移位的位是 10000,那么这个 &amp; 操作是这样的:

mask: 101001
bit:   10000
------------
AND:  000000

另一个例子:

mask: 101001
bit:    1000
------------
AND:  001000

因此,if 条件测试 mask 中位置 i 的位(从二进制表示的右侧开始)是否已设置。如果已设置,则值 mask &amp; (1 &lt;&lt; i)) 将等于 (1 &lt;&lt; i)(例如 1000),如果未设置此表达式的计算结果为 0。因此,通过执行 !== 0 测试,您可以有效地测试该位是放。只有在这种情况下,输入数组中的相应元素才会添加到子集中。

示例运行

假设我们使用这些参数调用函数:

(['a','b','c'], 2);

那么total = 8,mask从2到7。换算成二进制,mask取以下二进制值:

010
011
100
101
110
111

请注意,上表中的每一位(“列”)对应于数组的一个元素,其值(0 或 1)决定该元素是否应在子数组中。

mask 的其他值并不有趣:较小的值(如二进制 001 或 000)表示元素太少的子数组——我们至少需要两个。较高的值(如 1000、1001、...)有太多位:最左边的位不对应输入数组中的任何元素,因为数组中只有 3 个元素。所以这意味着我们在上表中有所有有趣的 mask 值。

代码中发生的下一件事是在 mask 的特定值中查找 1 位。我们使用变量 i 来表示位的从 0 开始的数字(它的位置从二进制表示的右侧开始)。这个 i 将从 2 开始并减少到 0(所以 2、1 和 0:每个都指向 mask 中的三个位之一):

i = input.length - 1; 
do { ... } while (i--);

对于给定的位号imask 中的位值被提取为:

mask & (1 << i)

所以让我们用 mask = 2(即二进制的 010)来完成上述所有操作。这些是计算出来的值:

result = []
i = 2
(1 << i) == 4 // 100 in binary: a 1-bit shifted twice to the left
mask & (1 << i) == 0 // because mask does not have that bit set 

i 的下一次迭代:

i = 1
(1 << i) == 2 // 010 in binary: a 1-bit shifted once to the left
mask & (1 << i) == 2 // because mask has that bit set 
result = ['b'] // because 'b' is the value at index i.

i 的最后一次迭代:

i = 0
(1 << i) == 1 // 001 in binary: a 1-bit not shifted at all
mask & (1 << i) == 0 // because mask does not have that bit set 

现在,检查 result 的长度,但是对于 mask 的这个特定值来说,它太小了:

result.length == 1 // see above, so nothing is added to the *results* (with 's')

mask == 2 的一切都已完成,所以现在对 mask == 3(二进制 011)重复上述操作:

result = []
i = 2
(1 << i) == 4 // 100 in binary: a 1-bit shifted twice to the left
mask & (1 << i) == 0 // because mask does not have that bit set 

i 的下一次迭代:

i = 1
(1 << i) == 2 // 010 in binary: a 1-bit shifted once to the left
mask & (1 << i) == 2 // because mask has that bit set 
result = ['b'] // because 'b' is the value at index i.

i 的最后一次迭代(这里的结果不同):

i = 0
(1 << i) == 1 // 001 in binary: a 1-bit not shifted at all
mask & (1 << i) == 1 // because mask has that bit set as well (different!)
result = ['b','a'] // because 'a' is the value at index i.

现在,再次检查result的长度,这次足够大了:

result.length == 2 // see above
results = [['b','a']]

...等等。

注意,results(复数)是一个数组数组,所以最终它会增长到:

results = [['b','a'], ['c','a'], ['c','b'], ['c','b','a']]

子数组的元素顺序相反,因为 i 的循环向后迭代。如果 i 从 0 开始递增(这可能没有任何问题),则子数组将正常排序。

限制

因为像 &lt;&lt;&amp; 这样的位运算要求它们的参数是 32 位整数,所以手头的代码只适用于最多包含 32 个元素的输入数组。

【讨论】:

  • 是否有理由total = 2^(input.length),我猜这也是由于二进制
  • 也许你可以让我通过一个测试用例。假设我们调用 (['a','b','c'],2)。现在总计 = 2^(3)。所以总共是 8。现在掩码 = 2,所以我们一直循环直到掩码为 8。我变成 7……呃现在读完你的答案后我还是有点困惑
  • 我扩展了我的答案。我希望这足够详细,以便掌握它。
  • 谢谢,您能否详细介绍一下 mask & (1
  • 1&lt;&lt;5 是数字 1(二进制 000000000001)向左移动 5 次,向右添加零:二进制 000000100000。如您所见,右侧添加了 5 个零。这等于 2^5。使用 i1&lt;&lt;i2^i
猜你喜欢
  • 1970-01-01
  • 2012-11-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-15
相关资源
最近更新 更多