【问题标题】:Organizing list of tuples组织元组列表
【发布时间】:2017-02-15 16:20:29
【问题描述】:

我有一个动态创建的元组列表。

列表显示为:

List = [(1,4), (8,10), (19,25), (10,13), (14,16), (25,30)]

list的每个元组(a, b)代表某个表的索引范围。

(a, b) and (b, d) 的范围在我的情况下与(a, d) 相同

我想合并第二个元素匹配第一个元素的元组。

所以,在上面的例子中,我想合并(8, 10), (10,13)得到(8,13)并删除(8, 10), (10,13)

(19,25) and (25,30) 合并应该产生(19, 30)

我不知道从哪里开始。元组不重叠。

编辑:我一直在尝试避免任何类型的 for 循环,因为我有一个非常大的列表

【问题讨论】:

  • 可以有[(1, 4), (4, 8), (8, 10)]这样的列表吗?
  • 这是一个快速而肮脏的想法:设置两个基于索引的 for 循环,第二个 for 循环从外部循环的索引开始。检查是否可以使用外部循环的索引与内部循环的索引合并元组。如果可以将 index[i] 替换为(a,d),然后重复。
  • 对,正确的处理方法应该是什么
  • 这很容易。你试过什么
  • 由于元组不重叠,您可以简单地对列表进行排序,如果它们重合,则合并相邻的元组。

标签: python algorithm list tuples


【解决方案1】:

您可以使用字典将不同的结束索引映射到以该索引结束的范围;然后只需迭代按起始索引排序的列表并相应地合并段:

def join_lists(lst):
    ending = {}  # will map end position to range
    for start, end in sorted(lst):  # iterate in sorted order
        if start in ending:
            ending[end] = (ending[start][0], end)  # merge
            del ending[start]  # remove old value
        else:
            ending[end] = (start, end)
    return list(ending.values())  # return remaining values from dict

或者,正如Tomer W in comments 所指出的,您可以不进行排序,通过迭代列表两次,使该解决方案只需要线性时间 (O(n)) w.r.t.列表的长度。

def join_lists(lst):
    ending = {}  # will map end position to range
    # first pass: add to dictionary
    for start, end in lst:
        ending[end] = (start, end)
    # second pass: lookup and merge
    for start, end in lst:
        if start in ending:
            ending[end] = (ending[start][0], end)
            del ending[start]
    # return remaining values from dict
    return list(ending.values())

两种情况下的示例输出:

>>> join_lists([(1,4), (8,10), (19,25), (10,13), (14,16), (25,30)])
[(1, 4), (8, 13), (14, 16), (19, 30)]
>>> join_lists(lst = [(1, 4), (4, 8), (8, 10)])
[(1, 10)]

【讨论】:

  • 只有一个引起O(N) 方法:) 向上。
  • 所以我可能得不到你的答案,我的想法是:第一次使用end val > section 填充哈希映射然后第二次通过每个start-valhashmap 中找到它的end-val
  • @TomerW 没想到。我做了排序,所以结尾条目已经在地图中,但只做两遍会好得多。谢谢!
【解决方案2】:

如果它们不重叠,那么您可以对它们进行排序,然后将相邻的组合起来。

这是一个生成新元组的生成器:

def combine_ranges(L):
    L = sorted(L)  # Make a copy as we're going to remove items!
    while L:
        start, end = L.pop(0)  # Get the first item
        while L and L[0][0] == end:
            # While the first of the rest connects to it, adjust
            # the end and remove the first of the rest
            _, end = L.pop(0)
        yield (start, end)

print(list(combine_ranges(List)))

如果速度很重要,请使用collections.deque 而不是列表,以便.pop(0) 操作可以保持恒定速度。

【讨论】:

    【解决方案3】:

    如果你需要在评论中考虑到 skovorodkin 的例子,

    [(1, 4), (4, 8), (8, 10)]
    

    (或者更复杂的例子),那么一种有效的方法就是使用图表。

    假设您创建了一个有向图(可能使用networkx),其中每一对都是一个节点,并且从 (a, b) 到节点 (c, d ) 如果 b == c。现在运行topological sort,按顺序迭代,并相应合并。您应该注意正确处理具有两个(或更多)出边的节点。


    我知道您的问题表明您希望避免由于列表大小太长而出现循环。相反,对于长列表,我怀疑您是否会使用列表理解(或类似的东西)找到​​有效的线性时间解决方案。请注意,例如,您不能按线性时间对列表进行排序。


    这是一个可能的实现:

    假设我们开始

    l = [(1,4), (8,10), (19,25), (10,13), (14,16), (25,30)]
    

    它简化了以下删除重复项,所以让我们这样做:

    l = list(set(l))
    

    现在构建有向图:

    import networkx as nx
    import collections
    
    g = nx.DiGraph()
    

    顶点只是简单的对:

    g.add_nodes_from(l)
    

    要构建边,我们需要一个字典:

    froms = collections.defaultdict(list)
    for p in l:
        froms[p[0]].append(p)
    

    现在我们可以添加边了:

    for p in l:
        for from_p in froms[p[1]]:
            g.add_edge(p, from_p)
    

    接下来的两行是不需要的 - 它们只是在这里显示图表此时的样子:

    >>> g.nodes()
    [(25, 30), (14, 16), (10, 13), (8, 10), (1, 4), (19, 25)]
    
    >>> g.edges()
    [((8, 10), (10, 13)), ((19, 25), (25, 30))]
    

    现在,让我们按照拓扑排序对这些对进行排序:

    l = nx.topological_sort(g)
    

    最后,这是棘手的部分。结果将是一个 DAG。我们必须递归遍历事物,但记住我们已经访问过的内容。

    让我们创建一个我们访问过的字典:

    visited = {p: False for p in l}
    

    现在是一个递归函数,给定一个节点,返回从它可以到达的任何节点的最大范围边:

    def visit(p):
        neighbs = g.neighbors(p)
        if visited[p] or not neighbs:
            visited[p] = True
            return p[1]
        mx = max([visit(neighb_p) for neighb_p in neighbs])
        visited[p] = True
        return mx
    

    我们都准备好了。让我们为最终对创建一个列表:

    final_l = []
    

    并访问所有节点:

    for p in l:
        if visited[p]:
            continue
        final_l.append((p[0], visit(p)))
    

    这是最终结果:

    >>> final_l
    [(1, 4), (8, 13), (14, 16)]
    

    【讨论】:

    • @Zanam 就是这样。不幸的是,这不是世界上最短的代码。
    【解决方案4】:

    以下应该有效。它将元组分解成单独的数字,然后找到每个簇上的元组。即使有困难的重叠,这也应该可以工作,例如[(4, 10), (9, 12)]

    这是一个非常简单的修复。

    # First turn your list of tuples into a list of numbers:
    my_list = []
    for item in List: my_list = my_list + [i for i in range(item[0], item[1]+1)]
    
    # Then create tuple pairs:
    output = []
    a = False
    for x in range(max(my_list)+1):
        if (not a) and (x in my_list): a = x
        if (a) and (x+1 not in my_list):
            output.append((a, x))
            a = False
    
    print output
    

    【讨论】:

      【解决方案5】:

      首先对列表进行排序,如果相邻的 (min1, max1), (min2, max2) 对重叠,则将它们合并在一起。

      MIN=0
      MAX=1
      
      def normalize(intervals):
          isort = sorted(intervals)
          for i in range(len(isort) - 1): 
              if isort[i][MAX] >= isort[i + 1][MIN]:
                  vmin = isort[i][MIN]
                  vmax = max(isort[i][MAX], isort[i + 1][MAX])
                  isort[i] = None
                  isort[i + 1] = (vmin, vmax)
          return [r for r in isort if r is not None]
      
      List1 = [(1,4), (8,10), (19,25), (10,13), (14,16), (25,30)]
      List2 = [(1, 4), (4, 8), (8, 10)]
      print(normalize(List1))
      print(normalize(List2))
      
      #[(1, 4), (8, 13), (14, 16), (19, 30)]
      #[(1, 10)]
      

      【讨论】:

        【解决方案6】:

        非递归方法,使用排序(我添加了更多节点来处理复杂情况):

        l = [(1,4), (8,10), (19,25), (10,13), (14,16), (25,30), (30,34), (38,40)]
        l = sorted(l)
        
        r=[]
        idx=0
        
        while idx<len(l):
            local=idx+1
            previous_value = l[idx][1]
            # search longest string
            while local<len(l):
                if l[local][0]!=previous_value:
                    break
                previous_value = l[local][1]
                local+=1
            # store tuple
            r.append((l[idx][0],l[local-1][1]))
            idx = local
        
        
        print(r)
        

        结果:

        [(1, 4), (8, 13), (14, 16), (19, 34), (38, 40)]
        

        唯一的缺点是没有保留原始排序顺序。不知道有没有问题。

        【讨论】:

          【解决方案7】:

          这是一种优化的递归方法:

          In [44]: def find_intersection(m_list):
                       for i, (v1, v2) in enumerate(m_list):
                           for j, (k1, k2) in enumerate(m_list[i + 1:], i + 1):
                               if v2 == k1:
                                   m_list[i] = (v1, m_list.pop(j)[1])
                                   return find_intersection(m_list)
                       return m_list
          

          演示:

          In [45]: lst = [(1,4), (8,10), (19,25), (10,13), (14,16), (25,30)]
          
          In [46]: find_intersection(lst)
          Out[46]: [(1, 4), (8, 13), (19, 30), (14, 16)]
          

          【讨论】:

            猜你喜欢
            • 2018-01-10
            • 1970-01-01
            • 2018-03-10
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2011-01-12
            • 2023-01-26
            • 2014-03-17
            相关资源
            最近更新 更多