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_ith
和 with_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
调用中使用列表推导式而不是生成器表达式很重要。生成器将继续迭代新添加的项目,从而导致无限循环(直到您的系统用完内存来扩展列表)。
我正在编写一个小函数来查找数字列表 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_ith
和 with_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
调用中使用列表推导式而不是生成器表达式很重要。生成器将继续迭代新添加的项目,从而导致无限循环(直到您的系统用完内存来扩展列表)。