根据关键函数排除包含重复项的组合

时间:2017-01-09 02:31:51

标签: python key combinations itertools

我想迭代列表的子序列,但我有一个由外部函数定义的唯一性概念,我想忽略在函数下具有相同值的多个元素的组合。

例如,我有一个名单列表,我想迭代这三个名称的所有组合,这样所有三个名字都以不同的字母开头。以下代码实现了这一点:

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个名字的所有组合慢。有没有一种方法实际上会更快?要创建一个迭代器,排除我想要首先跳过的那些?

2 个答案:

答案 0 :(得分:0)

是的,我能想到的一个解决方案需要创建一个字典,将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']})

答案 1 :(得分:0)

您可以使用itertools中的combinationsgroupbyproduct来获取以下内容:

[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,它将返回包含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