[英]Excluding combinations with duplicates based on a key function
我想迭代列表的子序列,但我有一个由外部函数定义的唯一性概念,我想忽略在函数下具有相同值的多个元素的组合。
例如,我有一个名称列表,我想迭代这三个名称的所有组合,以便所有三个名称以不同的字母开头。 以下代码完成此操作:
import itertools
names = ["Anabel",
"Alison",
"Avery",
"Abigail",
"Aimee",
"Alice",
"Bethany",
"Beatrice",
"Claudia",
"Carolyn",
"Diane",
"Dana"]
f = lambda x : x[0]
for i in itertools.combinations(names, 3):
if ((f(i[0]) != f(i[1])) and
(f(i[0]) != f(i[2])) and
(f(i[1]) != f(i[2]))):
print i
我实际上在这里做的是迭代3个名称的所有可能组合,并丢弃那些没有的名称,这当然比迭代3个名称的所有组合慢。 有没有一种方法实际上会更快? 要创建一个迭代器,排除我想要首先跳过的那些?
是的,有可能,我能想到的一个解决方案需要创建一个字典,将f(value)
和实际名称分组为字典。 我将在这里使用iteration_utilities.groupedby
,但是使用collections.defaultdict
也很容易自己做(我会在答案的底部显示)。
>>> from iteration_utilities import groupedby
>>> equivalent = groupedby(names, f)
>>> equivalent
{'A': ['Anabel', 'Alison', 'Avery', 'Abigail', 'Aimee', 'Alice'],
'B': ['Bethany', 'Beatrice'],
'C': ['Claudia', 'Carolyn'],
'D': ['Diane', 'Dana']}
然后迭代该字典中(排序)键的组合,然后使用itertools.product
对每个前缀的所有名称进行迭代:
import itertools
for comb in itertools.combinations(sorted(equivalent), 3):
for uniquecomb in itertools.product(*[equivalent[i] for i in comb]):
print(uniquecomb)
使用已sorted
,因为否则在运行之间出现的顺序不是确定的。
为了表明这更快,我使用了以下设置:
def unique_combs(names, f):
equivalent = groupedby(names, f)
for comb in itertools.combinations(sorted(equivalent), 3):
for uniquecomb in itertools.product(*[equivalent[i] for i in comb]):
pass
def unique_combs_original(names, f):
for i in itertools.combinations(names, 3):
if ((f(i[0]) != f(i[1])) and
(f(i[0]) != f(i[2])) and
(f(i[1]) != f(i[2]))):
pass
names = ["Anabel", "Alison", "Avery", "Abigail", "Aimee", "Alice",
"Bethany", "Beatrice",
"Claudia", "Carolyn",
"Diane", "Dana"]
f = lambda x : x[0]
%timeit unique_combs(names, f) # 10000 loops, best of 3: 59.4 µs per loop
%timeit unique_combs_original(names, f) # 1000 loops, best of 3: 417 µs per loop
但是如果有很多待丢弃的组合,它也可以更好地扩展:
names = names * 10 # more duplicates
%timeit unique_combs(names, f) # 100 loops, best of 3: 9.74 ms per loop
%timeit unique_combs_original(names, f) # 1 loop, best of 3: 577 ms per loop
我提到defaultdict
而不是groupedby
,因为completness它可以像这样创建:
from collections import defaultdict
>>> names = ["Anabel", "Alison", "Avery", "Abigail", "Aimee", "Alice",
... "Bethany", "Beatrice",
... "Claudia", "Carolyn",
... "Diane", "Dana"]
>>> equivalent = defaultdict(list)
>>> for name in names:
... equivalent[f(name)].append(name)
>>> equivalent
defaultdict(list,
{'A': ['Anabel', 'Alison', 'Avery', 'Abigail', 'Aimee', 'Alice'],
'B': ['Bethany', 'Beatrice'],
'C': ['Claudia', 'Carolyn'],
'D': ['Diane', 'Dana']})
您可以使用itertools
combinations
, groupby
和product
来跟随单行:
[p for c in combinations((tuple(g) for _, g in groupby(names, lambda x: x[0])), 3)
for p in product(*c)]
在上面的groupby
组中,名称基于第一个字母并返回(key, group)
元组的可迭代。 每个group
本身都是一个可迭代的,然后转换为元组,以便可以多次迭代。 请注意,这假定以相同字母开头的名称在列表中彼此相邻。 如果不是这种情况,您可以在使用groupby
之前对名称进行排序。
这是groupby
本身的一个例子:
>>> groups = list(tuple(g) for _, g in groupby(names, lambda x: x[0]))
>>> groups
[('Anabel', 'Alison', 'Avery', 'Abigail', 'Aimee', 'Alice'), ('Bethany', 'Beatrice'), ('Claudia', 'Carolyn'), ('Diane', 'Dana')]
接下来,结果组被赋予combinations
,这些combinations
将返回组中包含3个元素的所有子序列:
>>> combs = list(combinations(groups, 3))
>>> combs
[(('Anabel', 'Alison', 'Avery', 'Abigail', 'Aimee', 'Alice'), ('Bethany', 'Beatrice'), ('Claudia', 'Carolyn')), (('Anabel', 'Alison', 'Avery', 'Abigail', 'Aimee', 'Alice'), ('Bethany', 'Beatrice'), ('Diane', 'Dana')), (('Anabel', 'Alison', 'Avery', 'Abigail', 'Aimee', 'Alice'), ('Claudia', 'Carolyn'), ('Diane', 'Dana')), (('Bethany', 'Beatrice'), ('Claudia', 'Carolyn'), ('Diane', 'Dana'))]
最后,每个组合在解包并传递给product
,将产生对给定组的笛卡尔乘积:
>>> result = list(p for c in combs for p in product(*c))
>>> result[0]
('Anabel', 'Bethany', 'Claudia')
>>> len(result)
80
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.