获取列表子集的改进方法 方案一:返回一个生成器作为输出解决方案2:也接受生成器或迭代器作为输入衡量绩效

问题描述

以下是我获取列表子集的方式:

def subsets(s):
    if not s: return [[]]
    rest = subsets(s[1:])
    return rest + map(lambda x: [s[0]] + x,rest)

x = sorted(subsets([1,2,3]),key=lambda x: (len(x),x))
# [[],[1],[2],[3],[1,2],3],[2,3]]

它有效,但有点慢。我想知道在 python 中获取所有子集的更好的简单算法可能是什么(不只是使用像 itertools.combinations 这样的东西)。

解决方法

除非您确实需要,否则避免将子集列表存储在内存中会更有效率。

方案一:返回一个生成器作为输出

此代码通过递归生成器函数实现:

def subsets(s):
    if not s:
        yield []
    else:
        for x in subsets(s[1:]):
            yield x
            yield [s[0]] + x

这个函数只会在调用者需要的时候懒惰地做它的工作,并且永远不会在内存中存储更多的调用堆栈。

yield 子句中的第一个 else 用于生成 subsets(s[1:]) 的所有元素不变,即 s 的那些子集不包含 {{1} },而第二个产生包含 s[0] 的所有 s 子集。

当然,如果你想让这个列表排序,当你对结果调用 s[0] 时会损失效率,它会在内存中构建列表。

解决方案2:也接受生成器或迭代器作为输入

解决方案 1 的缺点是它仅适用于可索引的 sorted() 类型,例如列表和元组,其中 ss[0] 定义良好。

如果我们想让 s[1:] 更通用并接受任何可迭代的(包括迭代器),我们必须避免这种语法并使用 subsets 代替:

next()

衡量绩效

有了这一切,我想知道这些解决方案有多大的不同,所以我运行了一些基准测试。

我比较了您的初始代码,我称之为 def subsets(s): iterator = iter(s) try: item = next(iterator) except StopIteration: # s in empty,bottom out of the recursion yield [] else: # s is not empty,recurse and expand for x in subsets(iterator): yield x yield [item] + x ,我的解决方案 1,subsets_lists 在这里,我的解决方案 2,subsets_generator。为完整起见,我添加了 powerset function suggested in itertools

结果:

大小为 10 的列表的所有子集,每次调用重复 10 次:

subsets_gen_iterable

大小为 20 的列表的所有子集,每次调用重复 10 次:

Code block 'l = subsets_lists(range(10)) repeats=10' took: 5.58828 ms
Code block 'g = subsets_generator(range(10)) repeats=10' took: 0.05693 ms
Code block 'g = subsets_gen_iterable(range(10)) repeats=10' took: 0.01316 ms
Code block 'l = list(subsets_generator(range(10))) repeats=10' took: 4.86464 ms
Code block 'l = list(subsets_gen_iterable(range(10))) repeats=10' took: 3.76597 ms
Code block 'l = list(powerset(range(10))) size=10 repeats=10' took: 1.11228 ms

观察:
  • 您可以看到调用这两个生成器函数并没有做太多工作,除非将生成器输入到使用它的东西中,例如 Code block 'l = subsets_lists(range(20)) repeats=10' took: 12144.99487 ms Code block 'g = subsets_generator(range(20)) repeats=10' took: 65.18992 ms Code block 'g = subsets_gen_iterable(range(20)) repeats=10' took: 0.01784 ms Code block 'l = list(subsets_generator(range(20))) repeats=10' took: 10859.75128 ms Code block 'l = list(subsets_gen_iterable(range(20))) repeats=10' took: 10074.26618 ms Code block 'l = list(powerset(range(20))) size=20 repeats=10' took: 2336.81373 ms 构造函数。尽管 list() 似乎确实比 subsets_generator 预先做了很多工作。
  • 我的两个解决方案对您的代码产生了适度的速度提升
  • 基于 subsets_gen_iterable 的解决方案仍然要快得多。
实际基准测试代码:
itertools