Python 按公共元素对列表进行分组

Python 按公共元素对列表进行分组,python,python-2.7,Python,Python 2.7,假设我们有如下列表: S1 = [{'A_1'}, {'B_1', 'B_3'}, {'C_1'}, {'A_3'}, {'C_2'},{'B_2'}, {'A_2'}] S2 = [] 我想检查一下这个列表,并为每个集合检查该集合和该列表的其他集合之间的属性是否为true。然后,如果该属性成立,将这两个集合连接在一起,并将新集合与S1的其他集合进行比较。最后,将此新集合添加到S2 现在,作为一个例子,假设我们说,如果两个集合的所有元素都以相同的字母开头,那么属性在两个集合之间保持不变。 对于

假设我们有如下列表:

S1 = [{'A_1'}, {'B_1', 'B_3'}, {'C_1'}, {'A_3'}, {'C_2'},{'B_2'}, {'A_2'}]
S2 = []
我想检查一下这个列表,并为每个集合检查该集合和该列表的其他集合之间的属性是否为true。然后,如果该属性成立,将这两个集合连接在一起,并将新集合与S1的其他集合进行比较。最后,将此新集合添加到S2

现在,作为一个例子,假设我们说,如果两个集合的所有元素都以相同的字母开头,那么属性在两个集合之间保持不变。 对于上面描述的列表S1,我希望S2是:

S2 = [{'A_1', 'A_3', 'A_2'}, {'B_1', 'B_3', 'B_2'}, {'C_1','C_2'}]
我们应该如何为此编写代码

这是我的密码。它工作得很好,但我认为它效率不高,因为它多次尝试添加set(['A_3','A_2','A_1'])假设给定了Checker函数,它检查两个列表之间的属性。我上面提到的财产只是一个例子。我们以后可能想改变这一点。因此,我们应该将Checker作为一个函数。

 def Checker(list1, list2):

    flag = 1

    for item1 in list1:
        for item2 in list2:
            if item1[0] != item2[0]:
                flag =0

    if flag ==1:
        return 1
    else:
        return 0


S1 = [{'A_1'}, {'B_1', 'B_3'}, {'C_1'}, {'A_3'}, {'C_2'},{'B_2'}, {'A_2'}]
S2 = []

for i in range(0,len(S1)):

    Temp = S1[i]

    for j in range(0,i-1) + range(i+1,len(S1)):

        if Checker(Temp,S1[j]) == 1:

            Temp = Temp.union(S1[j])

    if Temp not in S2:
        S2.append(Temp)

print S2
输出:

[set(['A_3', 'A_2', 'A_1']), set(['B_1', 'B_2', 'B_3']), set(['C_1', 'C_2'])]
[['B_1', 'B_3', 'B_2'], ['A_1', 'A_3', 'A_2'], ['C_1', 'C_2']]
您可以展平(有很多方法,但一种简单的方法是使用
it.chain(*nested_list)
)和
sorted
仅使用属性作为
键,然后使用
it.groupby()
和相同的
键创建新列表:

In []:
import operator as op
import itertools as it
prop = op.itemgetter(0)
[set(v) for _, v in it.groupby(sorted(it.chain(*S1), key=prop), key=prop)]

Out[]:
[{'A_1', 'A_2', 'A_3'}, {'B_1', 'B_2', 'B_3'}, {'C_1', 'C_2'}]
输出:

[set(['A_3', 'A_2', 'A_1']), set(['B_1', 'B_2', 'B_3']), set(['C_1', 'C_2'])]
[['B_1', 'B_3', 'B_2'], ['A_1', 'A_3', 'A_2'], ['C_1', 'C_2']]

如果考虑性能,我建议使用python中的Canonical分组方法:使用
defaultdict

>>> S1 = [{'A_1'}, {'B_1', 'B_3'}, {'C_1'}, {'A_3'}, {'C_2'},{'B_2'}, {'A_2'}]
>>> from collections import defaultdict
>>> grouper = defaultdict(set)
>>> from itertools import chain
>>> for item in chain.from_iterable(S1):
...     grouper[item[0]].add(item)
...
>>> grouper
defaultdict(<class 'set'>, {'C': {'C_1', 'C_2'}, 'B': {'B_1', 'B_2', 'B_3'}, 'A': {'A_1', 'A_2', 'A_3'}})
如果你真的想要一个列表,你可以直接得到它:

>>> S2 = list(grouper.values())
>>> S2
[{'C_1', 'C_2'}, {'B_1', 'B_2', 'B_3'}, {'A_1', 'A_2', 'A_3'}]
假设N是所有嵌套集合中的项数,则此解为O(N)


我已尝试降低
Checker()
函数的复杂性。

是您的财产1。对称和2。及物的i、 e.1<代码>道具(a,b)
当且仅当
道具(b,a)
和2<代码>道具(a,b)
道具(b,c)
意味着
道具(a,c)
?如果是这样,您可以编写一个函数,它接受一个集合并为相应的等价类提供一些代码。例如

  1 S1 = [{'A_1'}, {'B_1', 'B_3'}, {'C_1'}, {'A_3'}, {'C_2'},{'B_2'}, {'A_2'}]
  2
  3 def eq_class(s):
  4     fs = set(w[0] for w in s)
  5     if len(fs) != 1:
  6         return None
  7     return fs.pop()
  8
  9 S2 = dict()
 10 for s in S1:
 11     cls = eq_class(s)
 12     S2[cls] = S2.get(cls,set()).union(s)
 13
 14 S2 = list(S2.values())

这样做的一个优点是可以摊销
O(len(S1))
。还请注意,如果1或2失败,您的最终输出可能取决于
S1的顺序。

使用
itertools.groupby

from itertools import groupby

S1 = [['A_1'], ['B_1', 'B_3'], ['C_1'], ['A_3'], ['C_2'],['B_2'], ['A_2']]

def group(data):
    # Flatten the data
    l = list((d for sub in data for d in sub))
    # Sort it
    l.sort()

    groups = []
    keys = []
    # Iterates for each group found only
    for k, g in groupby(l, lambda x: x[0]):
        groups.append(list(g))
        keys.append(k)

    # Return keys group data
    return keys, [set(x) for x in groups]

keys, S2 = group(S1)
print "Found the following keys", keys
print "S2 = ", S2
这里的主要思想是减少og
append
s的数量,因为这会严重影响性能。我们使用生成器将数据展平并进行排序。然后我们使用
groupby
对数据进行分组。循环对每个组只迭代一次。这里仍然有相当一部分数据拷贝可能被删除


另一个好处是,该函数还返回在数据中检测到的组键。

@cricket\u 007我的想法是从S1的第一个子列表开始,并与S1的其他子列表一起检查。如果该属性有效,则将所有这些子列表附加在一起,并将它们添加到S2。但是现在,我应该从S1中删除这些子列表。但是,如果我删除它们,列表S1会发生变化,我不知道如何在循环中迭代变化的列表。速度/效率是一个问题吗?@cricket_007我添加了我迄今为止所做的代码。@Grimmy是的,它是is@m0_as您是否已经知道
A
B
C
?如。。。您是否提前知道阵列的分组依据?除了将
列表
替换为
之外,不做任何更改。注意:
set
s未排序。此解决方案是O(logn*n)而不是O(n),如果您仅使用
dict
(或
defaultdict
)进行分组,则可能是O(n)。另外,
reduce(concat,nested_list)
也同样低效,因为它使用列表串联,这是一种线性操作。最好使用
itertools.chain.from\u iterable
。只是指出这一点,因为OP声明性能是一个考虑因素。我没有得到你的解决方案。你能假设函数检查器已经给出并更新了你的代码吗?这一行是做什么的?“prop=op.itemgetter(0)”
prop
是一个返回
项的属性值的函数。当前,该属性只是字符串的第一个字母,您可以使用
operator.itemgetter(0)
来获取它。如果您有更复杂的属性定义,只需更改
prop
。注:正如@juanpa.arrivillaga所指出的,这不是最有效的代码-基于
dict
的方法会更快。注2:您的checker函数效率非常低,只需编写自己的
prop
函数即可。此:
sum(map(list,S1),[])
效率非常低。使用
itertools.chain.from_iterable
不需要
map(list…
只需使用
list(chain.from_iterable(S1))
你能假设函数检查器已经给出了吗(正如我在问题中提到的那样)并更新您的代码?该属性只是一个示例。我们可能希望稍后更改该属性。@m0\u当时我强烈建议对您的代码进行折射,使其与上述内容兼容。最好的方法是让
checker
返回您要分组的属性。
from itertools import groupby

S1 = [['A_1'], ['B_1', 'B_3'], ['C_1'], ['A_3'], ['C_2'],['B_2'], ['A_2']]

def group(data):
    # Flatten the data
    l = list((d for sub in data for d in sub))
    # Sort it
    l.sort()

    groups = []
    keys = []
    # Iterates for each group found only
    for k, g in groupby(l, lambda x: x[0]):
        groups.append(list(g))
        keys.append(k)

    # Return keys group data
    return keys, [set(x) for x in groups]

keys, S2 = group(S1)
print "Found the following keys", keys
print "S2 = ", S2