这个问题有一个非常有效的解决方案,只需要几行代码,但是解释起来相当复杂。反正我会去的。
假设你需要减少一个列表,比如说,6 个数字,除了一个元素之外,这些数字都是零。通过对称性,只需考虑三种情况:
1 0 0 0 0 0 0 1 0 0 0 0 0 0 1 0 0 0
1 0 0 0 0 1 1 0 0 0 0 1 1 0 0
1 0 0 0 0 1 0 0 1 0 1 0
1 0 0 1 1 0 1 1 1
1 0 0 1 0 0
1 1 0
在第一种情况下,边缘的单个“1”实际上并没有什么作用。它基本上只是保持不变。但在另外两种情况下,涉及的列表元素更多,情况也更复杂。列表的第二个元素中的“1”产生“1”的结果,但第三个元素中的“1”产生“0”的结果。是否有解释这种行为的简单规则?
是的,有。看看这个:
Row 0: 1
Row 1: 1 1
Row 2: 1 2 1
Row 3: 1 3 3 1
Row 4: 1 4 6 4 1
Row 5: 1 5 10 10 5 1
我相信你以前见过这个。这是帕斯卡三角形,其中每一行是通过添加从上面一行中提取的相邻元素获得的。三角形中间较大的数字反映了这样一个事实,即这些数字是通过将来自前面行的更广泛子集的值相加而获得的。
请注意,在第 5 行中,中间的两个数字都是偶数,而其他数字都是奇数。这与上面显示的三个示例的行为完全匹配;偶数个“1”的异或积为零,奇数个“1”的异或积为“1”。
为了让事情更清楚,让我们只考虑这个三角形中数字的奇偶性(即,“1”表示奇数,“0”表示偶数):
Row 0: 1
Row 1: 1 1
Row 2: 1 0 1
Row 3: 1 1 1 1
Row 4: 1 0 0 0 1
Row 5: 1 1 0 0 1 1
这实际上称为Sierpinski triangle。在这个三角形中出现零的地方,它告诉我们你的列表在这个位置是 '1' 还是 '0' 无关紧要;它对结果值没有影响,因为如果您根据列表中的所有初始值写出显示最终结果值的表达式,该元素将出现偶数次。
以第 4 行为例。除极端边缘外,每个元素都为零。这意味着如果您的列表有 5 个元素,则最终结果仅取决于列表中的第一个和最后一个元素。 (这同样适用于元素数量是 2 的幂次方大 1 的任何列表。)
谢尔宾斯基三角形的行很容易计算。正如oeis.org/A047999中提到的:
卢卡斯定理是 T(n,k) = 1 当且仅当 k 的二进制扩展中的 1 是 n 的二进制扩展中的 1 的子集;或等效地,k AND NOT n 为零,其中 AND 和 NOT 是按位运算符。
所以,在冗长的解释之后,这是我的代码:
def xor_reduction(a):
n, r = len(a), 0
for k in range(n):
b = 0 if k & -n > 0 else 1
r ^= b & a.pop()
return r
assert xor_reduction([1, 0, 0, 1, 0, 1, 1, 1]) == 1
我说它很短。如果您想知道,第 4 行有 k & -n (k AND minus n) 而不是 k & ~n (k AND not n) 因为此函数中的 n 是列表中元素的数量,比行号,~(n-1) 与 -n 相同(至少在 Python 中)。