我在python wiki上找到了这一行函数,它创建了一组可以从作为参数传递的列表中创建的集合。
f = lambda x: [[y for j, y in enumerate(set(x)) if (i >> j) & 1] for i in range(2**len(set(x)))]
有人可以解释一下这个功能是如何运作的吗?
答案 0 :(得分:1)
要构建powerset,迭代2**len(set(x))
会给出集合的所有二进制组合。
range(2**len(set(x))) == [00000, 00001, 00010, ..., 11110, 11111]
现在您只需要测试该位是否在i
中设置,以确定是否需要将其包含在集合中,例如:
>>> i = 0b10010
>>> [y for j, y in enumerate(range(5)) if (i >> j) & 1]
[1, 4]
虽然我不确定每次迭代调用set(x)
的效率如何。有一个小黑客可以避免这种情况:
f = lambda x: [[y for j, y in enumerate(s) if (i >> j) & 1] for s in [set(x)] for i in range(2**len(s))]
使用itertools
的其他几种形式:
import itertools as it
f1 = lambda x: [list(it.compress(s, i)) for s in [set(x)] for i in it.product((0,1), repeat=len(s))]
f2 = lambda x: list(it.chain.from_iterable(it.combinations(set(x), r) for r in range(len(set(x))+1)))
注意:如果删除list()
,则最后一个可以返回一个可迭代vs列表,具体取决于用例可以节省一些内存。
查看25个随机数0-50列表的一些时间:
%%timeit binary: 1 loop, best of 3: 20.1 s per loop
%%timeit binary+hack: 1 loop, best of 3: 17.9 s per loop
%%timeit compress/product: 1 loop, best of 3: 5.27 s per loop
%%timeit chain/combinations: 1 loop, best of 3: 659 ms per loop
答案 1 :(得分:1)
让我们重写一下,然后逐步分解:
f = lambda x: [[y for j, y in enumerate(set(x)) if (i >> j) & 1] for i in range(2**len(set(x)))]
相当于:
def f(x):
n = len(set(x))
sets = []
for i in range(n): # all combinations of members of the set in binary
set_i = []
for j, y in enumerate(set(x)):
if (i>>j) & 1: #check if bit nr j is set
set_x.append(y)
sets.append(set_i)
return sets
对于像[1,2,3,4]
这样的输入列表,会发生以下情况:
n=4
range(2**n)=[0,1,2,3...15]
,二进制是:
0,1,10,11,100...1110,1111
枚举使用其索引生成y的元组,因此在我们的例子中:
[(0,1),(1,2),(2,3),(3,4)]
(i>>j) & 1
部分可能需要一些解释。
(i>>j)
将i
j
个数字向右移动,例如十进制:4>>2=1
或二进制:100>>2=001
。 &
是逐位and
运算符。这将检查两个操作数的每一位,如果它们是1并将结果作为数字返回,就像过滤器一样:10111 & 11001 = 10101
。
在我们的示例中,它检查位置j
的位是否为1.如果是,则将相应的值添加到结果列表中。这样,组合的二进制映射将转换为列表列表,并返回。