如何在python中将列表拆分为没有重复元素的子集
Posted
技术标签:
【中文标题】如何在python中将列表拆分为没有重复元素的子集【英文标题】:How to split a list into subsets with no repeating elements in python 【发布时间】:2011-11-15 08:20:51 【问题描述】:我需要获取一个列表(最多 n=31
)并返回 n=3
的所有可能子集的代码,而没有任何两个元素在同一子集中重复两次(想想以 3 人一组的人与新的人每次):
list=[1,2,3,4,5,6,7,8,9]
然后返回
[1,2,3][4,5,6][7,8,9]
[1,4,7][2,3,8][3,6,9]
[1,6,8][2,4,9][3,5,7]
但不是:
[1,5,7][2,4,8][3,6,9]
因为 1 和 7 已经一起出现了(同样,3 和 9)。
我也想对n=2
的子集执行此操作。
谢谢!!
【问题讨论】:
“所有可能”与“已经一起出现”不一致。为什么我们选择包含[1,4,7][2,3,8][3,6,9]
并因此排除[1,5,7][2,4,8][3,6,9]
,而不是相反?
总数不是组大小的倍数怎么办?将临时演员视为一个较小的群体?轮换他们,但忽略他们不在一个组中时会发生什么?还是那组条件不是有效的输入?
感谢 cmets。现在,我们假设 N 是组大小的倍数(N=30,n=3)。
感谢 cmets。 @Thomas:现在,我们假设 N=30,n=3。
@Karl:好点...这不是我想过的点...但是我可以在选项之间随机选择。
【参考方案1】:
这是我想出的:
from itertools import permutations, combinations, ifilter, chain
people = [1,2,3,4,5,6,7,8,9]
#get all combinations of 3 sets of 3 people
combos_combos = combinations(combinations(people,3), 3)
#filter out sets that don't contain all 9 people
valid_sets = ifilter(lambda combo:
len(set(chain.from_iterable(combo))) == 9,
combos_combos)
#a set of people that have already been paired
already_together = set()
for sets in valid_sets:
#get all (sorted) combinations of pairings in this set
pairings = list(chain.from_iterable(combinations(combo, 2) for combo in sets))
pairings = set(map(tuple, map(sorted, pairings)))
#if all of the pairings have never been paired before, we have a new one
if len(pairings.intersection(already_together)) == 0:
print sets
already_together.update(pairings)
打印出来:
~$ time python test_combos.py
((1, 2, 3), (4, 5, 6), (7, 8, 9))
((1, 4, 7), (2, 5, 8), (3, 6, 9))
((1, 5, 9), (2, 6, 7), (3, 4, 8))
((1, 6, 8), (2, 4, 9), (3, 5, 7))
real 0m0.182s
user 0m0.164s
sys 0m0.012s
【讨论】:
应该是最佳答案。简洁且超快速。谢谢,您帮助我为会议创建分组讨论【参考方案2】:试试这个:
from itertools import permutations
lst = list(range(1, 10))
n = 3
triplets = list(permutations(lst, n))
triplets = [set(x) for x in triplets]
def array_unique(seq):
checked = []
for x in seq:
if x not in checked:
checked.append(x)
return checked
triplets = array_unique(triplets)
result = []
m = n * 3
for x in triplets:
for y in triplets:
for z in triplets:
if len(x.union(y.union(z))) == m:
result += [[x, y, z]]
def groups(sets, i):
result = [sets[i]]
for x in sets:
flag = True
for y in result:
for r in x:
for p in y:
if len(r.intersection(p)) >= 2:
flag = False
break
else:
continue
if flag == False:
break
if flag == True:
result.append(x)
return result
for i in range(len(result)):
print('%d:' % (i + 1))
for x in groups(result, i):
print(x)
n = 10 的输出: http://pastebin.com/Vm54HRq3
【讨论】:
你测试过 N=15, n=3 吗? @Fenikso,它的运行速度太慢了。我有足够的耐心找出一个解决方案:pastebin.com/tE7xv0WT @Fenikso,为什么是 5? IIUC, OP 表示任何 N 的“n=3 的所有可能子集”。 15除以3等于5。这就是我对问题的理解。【参考方案3】:这是我对您的问题的一个相当普遍的解决方案的尝试。
from itertools import combinations
n = 3
l = range(1, 10)
def f(l, n, used, top):
if len(l) == n:
if all(set(x) not in used for x in combinations(l, 2)):
yield [l]
else:
for group in combinations(l, n):
if any(set(x) in used for x in combinations(group, 2)):
continue
for rest in f([i for i in l if i not in group], n, used, False):
config = [list(group)] + rest
if top:
# Running at top level, this is a valid
# configuration. Update used list.
for c in config:
used.extend(set(x) for x in combinations(c, 2))
yield config
break
for i in f(l, n, [], True):
print i
但是,对于 n
的高值来说它非常慢,对于 n=31
来说太慢了。我现在没有时间尝试提高速度,但我可能会稍后再试。欢迎提出建议!
【讨论】:
【参考方案4】:我的妻子在为与九个人的会议安排分组讨论时遇到了这个问题;她不希望有成对的与会者重复。
我立即淘汰了 itertools,然后被难住了,来到 ***。但与此同时,我的非程序员妻子直观地解决了这个问题。关键见解是创建井字游戏网格:
1 2 3
4 5 6
7 8 9
然后只需将 3 组向下,3 组通过,3 组对角环绕,3 组对角向反方向环绕。
那么你就可以在你的脑海中做到这一点。
- : 123,456,789
| : 147,258,368
\ : 159,267,348
/ : 168,249,357
我想下一个问题是你能把这样的视觉方法走多远?是否依赖于所需的子集大小 * 子集数 = 总元素数的巧合?
【讨论】:
以上是关于如何在python中将列表拆分为没有重复元素的子集的主要内容,如果未能解决你的问题,请参考以下文章
如何将字符串拆分为列表并在python中将两个已知令牌合并为一个?