Python 中 lists/sets 的联合集

Union sets of lists/sets in Python

我正在编写一个小函数来查找数字列表 S 的所有子集,输出是一个列表列表。

def subsets(S):
    if S is None or len(S) == 0:
        return [[]]

    output_list = []
    sub = [[[], [S[0]]]]
    for i in xrange(1, len(S)):
        without_ith = sub[i - 1]
        with_ith = [element + [S[i]] for element in without_ith]

        # convert to set of tuples, for set union
        without_set = set(tuple(element) for element in without_ith)
        with_set = set(tuple(element) for element in with_ith)
        new_set = without_set | with_set

        # convert back to list of lists
        new = list(list(element) for element in new_set)
        sub.append(new)

    # sort each sublist into non-descending order
    # output_list = [sorted(element) for element in sub[-1]]
    for element in sub[-1]:
        output_list.append(sorted(element))
    return output_list

此帖子的接受答案中描述了该算法:Finding all the subsets of a set

让我烦恼的是从list of lists到set of tuples的转换,然后对两组tuples进行并集,再转换回lists list。所有这些都发生在每次迭代中。原因是在 Python 中,集合必须包含不可变的对象,这些对象是可散列的,以便与其他集合执行集合操作。但是列表和集合是可变的和不可散列的,需要元组或冻结集作为此类集合的元素。对于我的代码,我首先改变元素列表并将它们转换为联合的元组,然后再转换回列表。我想知道是否有解决方法?它看起来不是很干净和高效。

(一个小疑问是我注释掉的列表理解 # output_list = [sorted(element) for element in sub[-1]]。我正在使用 PyCharm 并且它建议用 for 循环替换列表理解。任何原因?我认为 list理解总是更好。)

看起来列表理解比追加项目更快,因为使用它不需要将列表追加函数加载到内存中。检查 this great article 深度列表理解与附加比较。

因此,对于您的特定问题,我想列表理解速度更快。

我喜欢 "counting" 处理 "returning all subsets" 等任务的方法。假设 S 是一个没有重复的数字列表:

def subsets(S):   # S is a list of `whatever`
    result = []
    S = sorted(S)  # iff S can't be assumed to be sorted to start
    # S = sorted(set(S)) if duplicates are possible and must be pruned
    for i in range(2**len(S)):
        asubset = []
        for j, x in enumerate(S):
            if i & 1<<j: asubset.append(x)
        result.append(asubset)
    return result

本质上,这利用了 N 事物的子集与从 0 到 2**N - 1 的整数的二进制形式之间的 1-1 对应关系。

您的 without_ithwith_ith 列表之间没有重复的项目,因为前者的列表从不包含 S[i] 而后者的列表总是包含。这意味着组合它们时无需使用 set 对象,只需将一个列表连接到另一个列表即可!或者,您可以使用单个列表变量并 extend 它具有列表理解:

def subsets(S):
    results = [[]]
    for x in S:
        results.extend([item + [x] for item in results])
    return results

如果您的输入列表已排序,则所有子集也将排序。如果输入并不总是按顺序排列,而您需要输出按顺序排列,请在 sorted(S) 上循环而不是直接在 S 上循环。子集中的项目将始终按照它们被迭代的相同顺序出现。

请注意,在 extend 调用中使用列表推导式而不是生成器表达式很重要。生成器将继续迭代新添加的项目,从而导致无限循环(直到您的系统用完内存来扩展列表)。