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
这里的主要思想是减少ogappend
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