通过应用传递闭包创建唯一数字列表

时间:2017-02-06 13:38:17

标签: python list loops transitive-closure

我有一个元组列表(每个元组由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)]

我知道我可以使用循环和冗余匹配来完成此操作。我只想要一些更好的解决方案,处理/计算量很低(如果有的话)。

4 个答案:

答案 0 :(得分:3)

您可以使用数据结构,以便更有效地执行合并。在这里你创建了一些相反的树。因此,在您的示例中,您首先要创建列出的数字:

1  2  3  4  5  8  10

现在,如果您遍历(1,2)元组,则会在某种字典中查找12。你搜索他们的祖先(这里没有)然后你创建了某种合并节点

1  2  3  4  5  8  10
 \/
 12

接下来我们合并(1,3),以便我们查找112)和33)的祖先并执行另一次合并:

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)时,我们都可以轻松地将两个树uv关联起来(如果是不存在,通过将一棵树的根节点指向另一棵树来创建一个单节点树。最后,我们应该穿过森林来获得结果。

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组...