我有一个元组列表(每个元组由2个数字组成),如:
array = [(1, 2), (1, 3), (2, 4), (5, 8), (8, 10)]
可以说,这些数字是某些数据库对象(记录)的ID,而在元组内部,有重复对象的ID。这意味着1和2是重复的。 1和3是重复的,这意味着2和3也是重复的。
如果a == b且b == c则a == c
现在我想将所有这些重复的对象id合并到一个单元组中,如下所示:
output = [(1, 2, 3, 4), (5, 8, 10)]
我知道我可以使用循环和冗余匹配来完成此操作。我只想要一些更好的解决方案,处理/计算量很低(如果有的话)。
答案 0 :(得分:3)
您可以使用数据结构,以便更有效地执行合并。在这里你创建了一些相反的树。因此,在您的示例中,您首先要创建列出的数字:
1 2 3 4 5 8 10
现在,如果您遍历(1,2)
元组,则会在某种字典中查找1
和2
。你搜索他们的祖先(这里没有)然后你创建了某种合并节点:
1 2 3 4 5 8 10
\/
12
接下来我们合并(1,3)
,以便我们查找1
(12
)和3
(3
)的祖先并执行另一次合并:
1 2 3 4 5 8 10
\/ |
12 /
\/
123
接下来,我们合并(2,4)
和(5,8)
以及(8,10)
:
1 2 3 4 5 8 10
\/ | | \/ |
12 / | 58 /
\/ / \/
123 / 5810
\/
1234
您还会保留一个“合并头”列表,以便您轻松返回元素。
现在我们知道如何构建这样的数据结构,让我们实现一个。首先我们定义一个节点:
class Merge:
def __init__(self,value=None,parent=None,subs=()):
self.value = value
self.parent = parent
self.subs = subs
def get_ancestor(self):
cur = self
while cur.parent is not None:
cur = cur.parent
return cur
def __iter__(self):
if self.value is not None:
yield self.value
elif self.subs:
for sub in self.subs:
for val in sub:
yield val
现在我们首先为列表中的每个元素初始化一个字典:
vals = set(x for tup in array for x in tup)
为vals
中映射到Merge
的每个元素创建一个字典:
dic = {val:Merge(val) for val in vals}
和merge_heads
:
merge_heads = set(dic.values())
现在对于数组中的每个元组,我们查找作为祖先的相应Merge
对象,我们在其上创建一个新的Merge
,从{{1}中删除两个旧头设置并添加新的merge_head
:
merge
最后,我们完成后,我们可以为for frm,to in array:
mra = dic[frm].get_ancestor()
mrb = dic[to].get_ancestor()
mr = Merge(subs=(mra,mrb))
mra.parent = mr
mrb.parent = mr
merge_heads.remove(mra)
merge_heads.remove(mrb)
merge_heads.add(mr)
中的每个set
构建一个Merge
:
merge_heads
和resulting_sets = [set(merge) for merge in merge_heads]
将是(订单可能会有所不同):
resulting_sets
全部放在一起(没有[{1, 2, 3, 4}, {8, 10, 5}]
定义):
class
这最糟糕的情况是在 O(n 2 )中运行,但您可以平衡树,以便在<中找到祖先em> O(log n),而不是 O(n log n)。此外,您可以短路祖先列表,使其更快。
答案 1 :(得分:2)
您可以使用不相交的设置。
不相交集实际上是一种树结构。让我们将每个数字视为一个树节点,每次我们读边(u, v)
时,我们都可以轻松地将两个树u
和v
关联起来(如果是不存在,通过将一棵树的根节点指向另一棵树来创建一个单节点树。最后,我们应该穿过森林来获得结果。
from collections import defaultdict
def relation(array):
mapping = {}
def parent(u):
if mapping[u] == u:
return u
mapping[u] = parent(mapping[u])
return mapping[u]
for u, v in array:
if u not in mapping:
mapping[u] = u
if v not in mapping:
mapping[v] = v
mapping[parent(u)] = parent(v)
results = defaultdict(set)
for u in mapping.keys():
results[parent(u)].add(u)
return [tuple(x) for x in results.values()]
在上面的代码中,mapping[u]
存储了节点u
(父节点或根节点)的祖先。特别地,单节点树的节点的祖先本身就是。
答案 2 :(得分:1)
我认为实现这一目标的最有效方法是使用set
作为:
def transitive_cloure(array):
new_list = [set(array.pop(0))] # initialize first set with value of index `0`
for item in array:
for i, s in enumerate(new_list):
if any(x in s for x in item):
new_list[i] = new_list[i].union(item)
break
else:
new_list.append(set(item))
return new_list
示例运行:
>>> transitive_cloure([(1,2), (1,3), (2,4), (5,8), (8,10)])
[{1, 2, 3, 4}, {8, 10, 5}]
与其他答案的比较(在Python 3.4上):
这个回答:6.238126921001822
>>> timeit.timeit("moin()", setup="from __main__ import moin")
6.238126921001822
Willem's solution:29.115453064994654 (排除与宣布课程相关的时间)
>>> timeit.timeit("willem()", setup="from __main__ import willem")
29.115453064994654
hsfzxjy's solution:10.049749890022213
>>> timeit.timeit("hsfzxjy()", setup="from __main__ import hsfzxjy")
10.049749890022213
答案 3 :(得分:0)
请参阅我对Moinuddin答案的评论:此接受的答案无法验证您在http://rosettacode.org/wiki/Set_consolidation#Python上找到的测试。我没挖出来。
根据威廉的回答,我会提出一个新的主张。 这个命题的问题是get_ancestor调用中的递归性:为什么我们每次要问我们的祖先时,为什么我们只记得找到的最后一个根(并且仍然从以防万一它改变了)。确实,Willem的算法不是线性的(类似于nlogn或n²),而我们可以轻松地消除这种非线性。
另一个问题来自迭代器:如果树太深(我的用例中有问题),您会在迭代器内部收到Python异常(递归过多)。因此,我们不应该合并一棵完整的树,而应该合并子叶子(而不是使用具有2个叶子的分支,而是使用N个叶子来构建分支)。
我的代码版本如下:
class Merge:
def __init__(self,value=None,parent=None,subs=None):
self.value = value
self.parent = parent
self.subs = subs
self.root = None
if self.subs:
subs_a,subs_b = self.subs
if subs_a.subs:
subs_a = subs_a.subs
else:
subs_a = [subs_a]
if subs_b.subs:
subs_b = subs_b.subs
else:
subs_b = [subs_b]
self.subs = subs_a+subs_b
for s in self.subs:
s.parent = self
s.root = None
def get_ancestor(self):
cur = self if self.root is None else self.root
while cur.parent is not None:
cur = cur.parent
if cur != self:
self.root = cur
return cur
def __iter__(self):
if self.value is not None:
yield self.value
elif self.subs:
for sub in self.subs:
for val in sub:
yield val
def treeconsolidate(array):
vals = set(x for tup in array for x in tup)
dic = {val:Merge(val) for val in vals}
merge_heads = set(dic.values())
for settomerge in array:
frm = settomerge.pop()
for to in settomerge:
mra = dic[frm].get_ancestor()
mrb = dic[to].get_ancestor()
if mra == mrb:
continue
mr = Merge(subs=[mra,mrb])
merge_heads.remove(mra)
merge_heads.remove(mrb)
merge_heads.add(mr)
resulting_sets = [set(merge) for merge in merge_heads]
return resulting_sets
在小的合并中,这不会改变很多事情,但是我的经验表明,以大量的大量元素爬上树会花费很多:在我的情况下,我必须处理10万个元素集,每个元素包含2和1000个元素,每个元素可能出现1到1000组...