给定一个集合列表,我想获得彼此相交的集合列表。基本上我想要的是列表清单s.t.对于输出中的每个列表,该列表中的所有集都具有非空交集,并且在同一列表中至少有另一个集合。
我希望我能够解释我的问题。希望以下示例和帖子的其余部分应该进一步澄清它。
鉴于,
sets = [
set([1,3]), # A
set([2,3,5]), # B
set([21,22]), # C
set([1,9]), # D
set([5]), # E
set([18,21]), # F
]
我想要的输出是:
[
[
set([1,3]), # A, shares elements with B
set([2,3,5]), # B, shares elements with A
set([1,9]), # D, shares elements with A
set([5]), # E shares elements with B
],
[
set([21,22]), # C shares elements with F
set([18,21]), # F shares elements with C
]
]
输出中集合的顺序无关紧要。
我想用非常快的算法实现这个目标。表演是我的第一要求。
目前,我的解决方案创建了一个图表,其中包含与原始列表中的集合一样多的节点。然后,如果这些集合具有非空交集,则它在表示集合A和B的节点之间的该图中创建边缘。比它计算这样一个图形的连通分量,它给出了我预期的结果。
我想知道是否有更快的方法可以使用不涉及图形的算法。
最佳, 安德烈
答案 0 :(得分:1)
正如@MartijnPieters正确地说的那样,这个问题需要图表,networkx将会帮助你。
突出点
<强>实施强>
def intersecting_sets(sets):
import networkx as nx
G = nx.Graph()
# Nodes of the graph should be hashable
sets = map(frozenset, sets)
for to_node in sets:
for from_node in sets:
# off-course you don't want a self loop
# and only interested in intersecting nodes
if to_node != from_node and to_node & from_node:
G.add_edge(to_node, from_node)
# and remember to convert the frozen sets to sets
return [map(set, lst) for lst in nx.connected_components(G)]
<强>输出强>
>>> intersecting_sets(sets)
[[set([2, 3, 5]), set([1, 3]), set([5]), set([1, 9])], [set([21, 22]), set([18, 21])]]
>>> pprint.pprint(intersecting_sets(sets))
[[set([2, 3, 5]), set([1, 3]), set([5]), set([1, 9])],
[set([21, 22]), set([18, 21])]]
答案 1 :(得分:1)
我只看到在sets
上进行多次传递的解决方案(通常是O(N ^ 2))。因此,出于好奇,我想看看是否可以在sets
上只传递一次(即O(N))。
使用原始问题的输入集,这是一个如何做的例子 算法遍历列表中的每个集合。
[
set([1,3]), # A
set([2,3,5]), # B
set([21,22]), # C
set([1,9]), # D
set([5]), # E
set([18,21]), # F
]
我们从Set A(set([1,3])
)开始,并假设它是新结果列表L1的一部分。
我们还为我们的当前结果列表(目前为L1)维护一个指针C1。
这个指针的目的将在后面解释:
L1 = [set([1,3]),]
C1 = Pointer(L1)
对于集合A中的每个整数,我们还更新映射M:
{
1: C1, # Pointer(L1)
3: C1, # Pointer(L1)
}
下一个项目是设置B(set([2,3,5])
),我们假设它是新结果的一部分
列表L2。
L2 = [set([2,3,5]),]
C2 = Pointer(L2)
再一次,我们迭代这个集合的成员并更新我们的映射M.明确地,
我们看到2
不在M中,我们更新它以便我们:
{
1: C1, # Pointer(L1)
3: C1, # Pointer(L1)
2: C2, # Pointer(L2)
}
但是,当我们看到3
时,我们注意到它已经指向另一个结果
列表,L1。 这表示有一个交叉点,我们需要做两个
事情:
我们应该以M看起来像:
{
1: C1, # Pointer(L1)
3: C1, # Pointer(L1)
2: C2, # Pointer(L1)
}
L1现在应该是:
[[set([1,3]), set([2,3,5])]
这就是我们需要Pointer类的原因:我们不能只将C2分配给新的 指针()实例。如果我们这样做,那么M看起来像:
{
1: C1, # Pointer(L1)
3: C1, # Pointer(L1)
2: C2, # Pointer(L2) <-- this should be pointing to L1
}
相反,我们做了类似的事情:
C2.set(L1)
在我们处理了Set B和Set C之后,M的状态就应该先行了 看起来像:
{
1: C1, # Pointer(L1)
3: C1, # Pointer(L1)
2: C2, # Pointer(L1)
5: C2, # Pointer(L1)
21: C3, # Pointer(L3)
22: C3, # Pointer(L3)
}
结果列出:
[set([1,3]), set([2,3,5])] # L1
[set([21,22]), ] # L3
[set([2,3,5]), ] # L2 (but nothing in M points to it)
当我们看到Set D时,我们再次假设它是新结果列表L4的一部分
创建相应的指针,C4指向L4。但是,当我们看到
看到Set D包含与L1相交的1
,我们将C4更改为point
到L1并将Set D添加到L1。
M的状态是:
{
1: C1, # Pointer(L1)
3: C1, # Pointer(L1)
2: C2, # Pointer(L1)
5: C2, # Pointer(L1)
21: C3, # Pointer(L3)
22: C3, # Pointer(L3)
9: C4, # Pointer(L1)
}
L1现在是:
[set([1,3]), set([2,3,5]), set([1,9])]
一直走到最后,M的状态将如下所示:
{
1: C1, # Pointer(L1)
3: C1, # Pointer(L1)
2: C2, # Pointer(L1)
5: C2, # Pointer(L1)
21: C3, # Pointer(L3)
22: C3, # Pointer(L3)
9: C4, # Pointer(L1)
18: C6, # Pointer(L3)
}
列表如下:
[set([1,3]), set([2,3,5]), set([1,9]), set([5])] # L1
[set([21,22]), set([18, 21]) # L3
此时,您可以迭代M中的指针并找到所有唯一列表 正在被引用,这将是你的结果。
在原始代码中,我试图通过维护列表来避免这最后一步 独特的结果集即时。但是,我已经删除了该逻辑以保持代码更简单 因为无论如何,性能提升可能并不那么好。
这是更新的代码,其中包含Andrea在评论中提到的bug修复程序。
class Pointer(object):
"""
Implements a pointer to an object. The actual object can be accessed
through self.get().
"""
def __init__(self, o): self.o = o
def __str__(self): return self.o.__str__()
def __repr__(self): return '<Pointer to %s>' % id(self.o)
# These two methods make instances hashed on self.o rather than self
# so Pointers to the same object will be considered the same in
# dicts, sets, etc.
def __eq__(self, x): return x.o is self.o
def __hash__(self): return id(self.o)
def get(self): return self.o
def set(self, obj): self.o = obj.o
def id(self): return id(self.o)
def connected_sets(s):
M = {}
for C in s:
# We assume C belongs to a new result list
P = Pointer(list())
added = False # to check if we've already added C to a result list
for item in C:
# There was an intersection detected, so the P points to this
# intersecting set. Also add this C to the intersecting set.
if item in M:
# The two pointers point to different objects.
if P.id() != M[item].id():
# If this was a regular assignment of a set to a new set, this
# wouldn't work since references to the old set would not be
# updated.
P.set(M[item])
M[item].o.append(C)
added = True
# Otherwise, we can add it to P which, at this point, could point to
# a stand-alone set or an intersecting set.
else:
if not added:
P.o.append(C)
added = True
M[item] = P
return M
if __name__ == '__main__':
sets = [
set([1,3]), # A
set([2,3,5]), # B
set([21,22]), # C
set([1,9]), # D
set([5]), # E
set([18,21]), # F
]
#sets = [
# set([1,5]), # A
# set([1,5]), # B
#]
M = connected_sets(sets)
import pprint
pprint.pprint(
# create list of unique lists referenced in M
[x.o for x in set(M.values())]
)
输出为:
[[set([1, 3]), set([2, 3, 5]), set([1, 9]), set([5])],
[set([21, 22]), set([18, 21])]]
它似乎对我有用,但我很好奇它与你的解决方案相比如何表现。使用timeit
模块,我将其与使用networkx
的上述答案进行了比较,我的脚本在同一数据集上的速度提高了近3倍。