我有很多间隔(大约 5k 到 10k)。这些元素有开始和结束位置;像 (203, 405)。间隔的坐标存储在列表中。
我想确定每对间隔之间重叠部分的坐标和长度。这可以按如下方式完成:
# a small list for clarity, with length normally around 5000s
cList = ((20, 54), (25, 48), (67, 133), (90,152), (140,211), (190,230))
for i, c1 in enumerate(cList[:-1]): # a linear pairwise combination
for c2 in cList[i + 1:]:
left = max(c1[0], c2[0])
right = min(c1[1], c2[1])
overlap = right - left
if overlap > 0:
print "left: %s, right: %s, length: %s" % (left, right, overlap)
Run Code Online (Sandbox Code Playgroud)
导致:
left: 25, right: 48, length: 23
left: 90, right: 133, length: 43
left: 140, right: 152, length: 12
left: 190, right: 211, length: 21
Run Code Online (Sandbox Code Playgroud)
可以看出,它有效......因为这可能需要相当长的时间(20 秒),我的问题是,我将如何优化它?当第二个循环的开始位置超过第一个结束位置时,我试图切断第二个 for 循环:
if c1[1] < c2[0]:
break
Run Code Online (Sandbox Code Playgroud)
这大大减少了程序时间,但由此产生的重叠次数比以前减少了大约三倍,因此结果肯定是无效的。这是由长度比前面的元素大得多的元素引起的。
我确定有一些数学技巧可以解决这个问题
通常,如果您按起点对间隔进行排序,则此类问题会变得容易得多。
首先,让我们定义一个函数,以使事情更清楚:
def overlap( r1, r2 ):
left = max(r1[0], r2[0])
right = min(r1[1], r2[1])
over = right - left
return (left, right, over) if over>0 else None
Run Code Online (Sandbox Code Playgroud)
你描述的算法可以写成:
for i, c1 in enumerate(cList[:-1]):
for c2 in cList[i + 1:]:
o = overlap(c1,c2)
if not o is None:
print "left: %s, right: %s, length: %s" % o
Run Code Online (Sandbox Code Playgroud)
如果对元素进行排序,一旦找到不重叠的段,就可以“短路”,因为您知道列表中更远的那些将“远离”:
l= sorted(cList)
for i, c1 in enumerate(l[:-1]):
for c2 in l[i + 1:]:
o= overlap(c1,c2)
if o is None:
break
print "left: %s, right: %s, length: %s" % o
Run Code Online (Sandbox Code Playgroud)
当然,如果您的输入已经排序(看起来),您可以跳过该步骤。
请注意,一般情况下for,您可以使用更清晰的,而不是使用 double itertools.combinations。它保证了相同的排序。不幸的是,它不适合算法的优化版本,但你的可以写成
from itertools import combinations
for c1,c2 in combinations(cList, 2):
o= overlap(c1,c2)
if not o is None:
print "left: %s, right: %s, length: %s" % o
Run Code Online (Sandbox Code Playgroud)
最后,如果您想对区间执行快速通用操作,您可能还需要考虑使用区间树数据结构。pypi 上有一个 python 实现。