组织元组列表

时间:2016-10-06 19:27:24

标签: python algorithm list tuples

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

列表显示为:

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循环,因为我有一个非常大的列表

7 个答案:

答案 0 :(得分:6)

如果您需要在评论中考虑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)]

答案 1 :(得分:5)

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

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

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)操作可以保持恒定速度。

答案 2 :(得分:2)

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

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)]

答案 3 :(得分:2)

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

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)]

唯一的缺点是不保留原始排序顺序。我不知道这是不是一个问题。

答案 4 :(得分:2)

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

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)]

答案 5 :(得分:1)

首先对列表进行排序,如果它们重叠,则将相邻的(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 :(得分:1)

以下内容应该有效。它将元组分解为单个数字,然后在每个集群上找到元组绑定。这应该适用于困难的重叠,例如[(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