【问题标题】:Fastest method to update all list entries with union of all intersecting entries使用所有相交条目的并集更新所有列表条目的最快方法
【发布时间】:2021-11-02 19:18:04
【问题描述】:

我正在寻找一种快速的方法来遍历集合列表,并通过找到与列表中至少共享一个元素的任何其他元素的联合来扩展每个集合。

例如,假设我有四行数据,其中每一行对应一组唯一的元素

0, 5, 101
8, 9, 19, 21
78, 79
5, 7, 63, 64

第一行和最后一行有相交元素 5,所以在执行我的操作后我想要联合

0, 5, 7, 63, 64, 101
8, 9, 19, 21
78, 79
0, 5, 7, 63, 64, 101

现在,我几乎可以用两个循环做到这一点:

def consolidate_list(arr):
    """
    arr (list) : A list of lists, where the inner lists correspond to sets of unique integers
    """
    arr_out = list()
    for item1 in arr:
        item_additional = list() # a list containing all overlapping elements
        for item2 in arr:
            if len(np.intersect1d(item1, item2)) > 0:
                item_additional.append(np.copy(item2))
        out_val = np.unique(np.hstack([np.copy(item1)] + item_additional)) # find union of all lists

        arr_out.append(out_val)
        
return arr_out

这种方法的问题是它需要运行多次,直到输出停止变化。由于输入可能是锯齿状的(即每组不同数量的元素),我看不到向量化此函数的方法。

【问题讨论】:

  • 想到的第一个想法:(1)构建一个图,其中每个顶点都是您的集合之一,并且如果两个集合具有共同的元素,则两个集合共享一条边。 (2) 识别图的连通分量。 (3) 合并属于同一个连通分量的所有集合。您可以考虑使用模块networkx 来构建图并识别连接的组件。
  • 我已修正错字。谢谢!

标签: python arrays algorithm performance intersection


【解决方案1】:

这个问题是关于创建disjoint sets,所以我会使用联合查找方法。

现在 Python 并不是特别以速度快着称,但为了展示算法,这里是一个没有库的 DisjointSet 类的实现:

class DisjointSet:
    class Element:
        def __init__(self):
            self.parent = self
            self.rank = 0


    def __init__(self):
        self.elements = {}

    def find(self, key):
        el = self.elements.get(key, None)
        if not el:
            el = self.Element()
            self.elements[key] = el
        else: # Path splitting algorithm
            while el.parent != el:
                el, el.parent = el.parent, el.parent.parent
        return el

    def union(self, key=None, *otherkeys):
        if key is not None:
            root = self.find(key)
            for otherkey in otherkeys:
                el = self.find(otherkey)
                if el != root:
                    # Union by rank
                    if root.rank < el.rank:
                        root, el = el, root
                    el.parent = root
                    if root.rank == el.rank:
                        root.rank += 1

    def groups(self):
        result = { el: [] for el in self.elements.values() 
                          if el.parent == el }
        for key in self.elements:
            result[self.find(key)].append(key)
        return result

以下是您可以如何使用它来解决这个特定问题:

def solve(lists):
    disjoint = DisjointSet()
    for lst in lists:
        disjoint.union(*lst)
            
    groups = disjoint.groups()
    return [lst and groups[disjoint.find(lst[0])] for lst in lists]

调用示例:

data = [
    [0, 5, 101],
    [8, 9, 19, 21],
    [],
    [78, 79],
    [5, 7, 63, 64]
]
result = solve(data)

结果将是:

[[0, 5, 101, 7, 63, 64], [8, 9, 19, 21], [], [78, 79], [0, 5, 101, 7, 63, 64]]

请注意,我在输入列表中添加了一个空列表,以说明此边界情况保持不变。

注意:有一些库提供联合查找/不相交集功能,每个库都有稍微不同的 API,但我认为使用其中一个可以提供更好的性能。

【讨论】:

  • 我认为更好的维基百科链接是en.wikipedia.org/wiki/Disjoint-set_data_structure,它实际上是来自给定维基页面的链接。
  • 输入 [[1, 2], [2, 3], [3, 4]] 的 OP 代码输出为 [array([1, 2, 3]), array([1, 2, 3, 4]), array([2, 3, 4])]。相同输入的代码输出为[[1, 2, 3, 4], [1, 2, 3, 4], [1, 2, 3, 4]]。我不知道哪个是正确的。仅供参考。
  • @גלעד ברקן,问题的最后一段解释了提问者的代码产生的这个(中间)结果(它必须运行多次)。
  • 啊,谢谢你的解释。重新运行时输出变得相同。
  • @גלעדברקן 没错,我将编辑问题以使这一点更清楚。到目前为止,这看起来令人难以置信(并且比我的双循环方法更有效)。感谢您提供这个令人难以置信的答案。
【解决方案2】:

你的意思是?:

from itertools import combinations
l1 = [0, 5, 7, 63, 64, 101]
l2 = [8, 9, 19]
l3 = [78, 79]
l4 = [5, 4, 34]
print([v for x, y in combinations([l1, l2, l3, l4], 2) for v in {*x} & {*y}])

输出:

[5]

【讨论】:

  • 你的代码“重新发明”set.intersection :-)
  • @balderman 好吧,这不适用于常规路口
  • 无论如何,OP 正在寻找不同的东西。 “我对遍历集合列表很感兴趣,为每个条目找到与该条目相交的所有元素,然后用所有相交元素的并集替换该条目”
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-09-30
  • 1970-01-01
  • 2017-02-27
  • 1970-01-01
  • 1970-01-01
  • 2011-06-15
相关资源
最近更新 更多