【问题标题】:Iterative Solution to End-Overlapping Indices结束重叠指数的迭代解决方案
【发布时间】:2020-10-25 06:46:29
【问题描述】:

我有一个包含表示数字范围的元组的列表。我的目标是返回这个集合的所有(参见下面的注释;真正寻找最长的)可能的子集,这些子集仅与每个元组中的第二个值重叠或根本不重叠。我一直在使用的函数是递归解决这个问题的。

def get_all_end_overlapping_indices(lst, i, out):
    all_possibilities = []

    def _get_all_end_overlapping_indices_helper(list_in, i, out):
        r = -1
        if i == len(list_in):
            if out:
                if len(all_possibilities) == 0:
                    all_possibilities.append(out)
                else:                       
                    all_possibilities.append(out)

            return 

        n = i + 1

        while n < len(list_in) and r > list_in[n][0]:
            n += 1
        _get_all_end_overlapping_indices_helper(list_in, n, out)
        r = list_in[i][1]

        n = i + 1
        while n < len(list_in) and r > list_in[n][0]:
            n += 1
        _get_all_end_overlapping_indices_helper(list_in, n, out + [list_in[i]])

    _get_all_end_overlapping_indices_helper.count = 0
    lst.sort()
    _get_all_end_overlapping_indices_helper(list_in = lst, i = 0, out = [])
    
    return all_possibilities

lst = [(0.0, 2.0), (0.0, 4.0), (2.5, 4.5), (2.0, 5.75), (2.0, 4.0), (6.0, 7.25), (4.0, 5.5)] 得到以下结果

[(6.0, 7.25)]
[(4.0, 5.5)]
[(4.0, 5.5), (6.0, 7.25)]
[(2.5, 4.5)]
[(2.5, 4.5), (6.0, 7.25)]
[(2.0, 5.75)]
[(2.0, 5.75), (6.0, 7.25)]
[(2.0, 4.0)]
[(2.0, 4.0), (6.0, 7.25)]
[(2.0, 4.0), (4.0, 5.5)]
[(2.0, 4.0), (4.0, 5.5), (6.0, 7.25)]
[(0.0, 4.0)]
[(0.0, 4.0), (6.0, 7.25)]
[(0.0, 4.0), (4.0, 5.5)]
[(0.0, 4.0), (4.0, 5.5), (6.0, 7.25)]
[(0.0, 2.0)]
[(0.0, 2.0), (6.0, 7.25)]
[(0.0, 2.0), (4.0, 5.5)]
[(0.0, 2.0), (4.0, 5.5), (6.0, 7.25)]
[(0.0, 2.0), (2.5, 4.5)]
[(0.0, 2.0), (2.5, 4.5), (6.0, 7.25)]
[(0.0, 2.0), (2.0, 5.75)]
[(0.0, 2.0), (2.0, 5.75), (6.0, 7.25)]
[(0.0, 2.0), (2.0, 4.0)]
[(0.0, 2.0), (2.0, 4.0), (6.0, 7.25)]
[(0.0, 2.0), (2.0, 4.0), (4.0, 5.5)]
[(0.0, 2.0), (2.0, 4.0), (4.0, 5.5), (6.0, 7.25)]

由于我最终将处理更大的元组集合(而且运行速度很慢),我想实现一个迭代解决方案;不幸的是,我很难过。这个 sn-p 最初来自:Find all possible combinations that overlap by end and start。虽然它可以工作,但我发现理解它是如何工作的 很棘手。任何人都可以提供一些关于如何构建此问题的迭代解决方案的提示吗?

注意:我实际上只希望获得最长的输出(见下文)。我总是可以稍后过滤掉较短的(即位于最长的内部的那些),但如果它更容易,我很乐意将它们删除。

[(0.0, 2.0), (4.0, 5.5), (6.0, 7.25)]
[(0.0, 2.0), (2.5, 4.5), (6.0, 7.25)]
[(0.0, 2.0), (2.0, 5.75), (6.0, 7.25)]
[(0.0, 2.0), (2.0, 4.0), (4.0, 5.5), (6.0, 7.25)]
[(0.0, 4.0), (4.0, 5.5), (6.0, 7.25)]

【问题讨论】:

    标签: python list loops recursion iteration


    【解决方案1】:

    编辑我之前的回答并没有真正回答这个问题,所以这里有一段代码确实可以做到。

    我们需要找到的是非重叠元组序列的帕累托最优边界(Pareto 最优在包含的意义上)。

    • 首先,我们需要找到“源”元组,即可以位于元组序列开头的元组。
    sources = {
        (a, b)
        for (a, b) in tup_lst
        if not any(d <= a for (c, d) in tup_lst)
    }    
    
    • 同样,我们计算一组“汇”,即一组必须位于元组序列末尾的元组(它们不能有后继)。
    sinks = {
        (a, b)
        for (a, b) in tup_lst
        if not any(b <= c for (c, d) in tup_lst)
    }
    
    • 然后,对于每个元组a, b,我们计算一个min_successor 值。它对应于最小的d,因此存在一个c, db &lt;= c
    min_successor = {
        (a, b): min(d for c, d in tup_lst if c >= b)
        for (a, b) in set(tup_lst) - sinks
    }
    
    • 然后我们可以为每个元组计算其“后继者”列表。
    successors = {
        (a, b): [
            (c, d)
            for (c, d) in tup_lst
            if b <= c <= d and c < min_successor[(a, b)]
        ] for (a, b) in tup_lst
    }
    
    • 最后,我们可以检索最长的非重叠元组序列。
    def print_path_rec(node, path):
        if node in sinks:
            print(path + [node])
        else:
            for successor in successors[node]:
                print_path_rec(successor, path + [node])
    for source in sources:
        print_path_rec(source, [])
    

    输入:[(0.0, 2.0), (0.0, 4.0), (2.5, 4.5), (2.0, 5.75), (2.0, 4.0), (6.0, 7.25), (4.0, 5.5)]
    输出:

    [(0.0, 2.0), (2.5, 4.5), (6.0, 7.25)]
    [(0.0, 2.0), (2.0, 5.75), (6.0, 7.25)]
    [(0.0, 2.0), (2.0, 4.0), (4.0, 5.5), (6.0, 7.25)]
    [(0.0, 4.0), (4.0, 5.5), (6.0, 7.25)]
    

    我认为这个问题在多项式时间内无法解决,仅仅是因为输出的大小可能是输入大小的指数。尽管如此,它会运行得相当快,因为​​它需要O(n²) 来构建 4 个字典,然后每条路径都会在线性时间内输出。

    编辑结束,下面是旧解决方案

    我们可以在多项式时间内解决这个问题,方法是将其简化为 DAG(有向无环图)中最长路径的问题。

    首先,我们需要将问题建模为 DAG。每个元组代表一个顶点,我们构建从(a,b)(c, d) 的边当且仅当b &lt;= c

    然后我们可以看到,(1)得到的图是非循环的,通过构造,(2)从图中一个顶点到另一个顶点的最长路径将表示重叠元组的最长序列。

    幸运的是,最长路径问题(在一般情况下是 NP 难题)在 DAG 中并不难。 this document(第 4 页)详细描述了该问题。
    找到最长重叠元组序列的总体复杂度应该是:O(n²) 构建图,O(n²) 排序顶点,O(n²) 找到最长路径,所以在最坏的情况下O(n²)。这比您使用的递归方法要快得多,因为我们不想枚举所有组合,但我们只想要最长的组合。

    下面是一个 python 3 代码,它将计算最长的元组序列。如果我误解了元组上的“重叠”关系,则可以在 overlap_condition 函数中轻松修改它。

    def overlap_condition(tup1, tup2):
        if tup1 == tup2:
            return False
        a, b = tup1
        c, d = tup2
        return b <= c
    
    
    def adj_mat_from_tup_list(tup_lst):
        return [
            [
                1 if overlap_condition(tup_lst[i], tup_lst[j]) else 0
                for j in range(len(tup_lst))
            ] for i in range(len(tup_lst))
    
        ]
    
    
    def topological_sort(adj_mat):
        sorted_v = []
        sinks = {
            i for i in range(len(adj_mat))
            if not any(adj_mat[j][i] == 1 for j in range(len(adj_mat)))
        }
    
        while sinks:
            v = sinks.pop()
            sorted_v += [v]
            for j in range(len(adj_mat)):
                if adj_mat[v][j] == 1:
                    adj_mat[v][j] = 0
                    if not any(adj_mat[w][j] for w in range(len(adj_mat))):
                        sinks.add(j)
        return sorted_v
    
    
    def get_longest_path(adj_mat, sorted_v):
        dists = {v: 0 for v in range(len(adj_mat))}
        preds = {v: None for v in range(len(adj_mat))}
        for v in sorted_v:
            for u in range(len(adj_mat)):
                if adj_mat[u][v]:
                    dists[v] = max(dists[v], dists[u] + 1)
                    preds[v] = u
    
        current_v = {
            v for v in range(len(adj_mat))
            if dists[v] == max(dists.values())
        }.pop()
        result = [current_v]
        while preds[current_v] is not None:
            current_v = preds[current_v]
            result += [current_v]
        return result[::-1]
    
    
    def get_all_end_overlap_tups(tup_lst):
        sorted_v = topological_sort(adj_mat_from_tup_list(tup_lst))
        adj_mat = adj_mat_from_tup_list(tup_lst)
        return [tup_lst[i] for i in get_longest_path(adj_mat, sorted_v)]
    
    
    lst = [
        (0.0, 2.0), (0.0, 4.0), (2.5, 4.5), (2.0, 5.75),
        (2.0, 4.0), (6.0, 7.25), (4.0, 5.5)
    ]
    
    print(get_all_end_overlap_tups(lst))
    

    【讨论】:

    • 非常感谢@m.raynal!这对我很有帮助。使用这个框架,是否可以检索 DAG 中不重叠的其他路径?在我的帖子中,您可能会看到(在底部)我希望检索所有(最长的)非重叠路径,例如 [(0.0, 2.0), (2.5, 4.5), (6.0, 7.25)] 和 [( 0.0, 2.0), (4.0, 5.5), (6.0, 7.25)].
    • 嗨,是的,使用这个框架,可以找到最长路径的帕累托最优边界。但是我今天早上一直在考虑您的问题,并且在此框架之外想出了一个更简单,更好的解决方案,我将在今晚发布。
    • @LukePoeppel 你可以看看新的解决方案,我想这一次回答了你的问题 :-)
    • 非常感谢您编辑的答案。这正是我想要的! :-)
    猜你喜欢
    • 2013-07-22
    • 1970-01-01
    • 2022-11-18
    • 2016-03-19
    • 1970-01-01
    • 2020-06-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多