Python间隔交集

时间:2013-08-23 09:43:14

标签: python algorithm intervals

我的问题如下:

包含间隔列表的文件:

1 5
2 8
9 12
20 30

一系列

0 200

我想做一个这样的交集,它会报告给定范围内我的间隔之间的位置[开始结束]。

例如:

8 9
12 20
30 200

除了任何想法如何咬这个之外,阅读一些关于优化的想法也会很好,因为一如既往,输入文件将是巨大的。

4 个答案:

答案 0 :(得分:2)

此解决方案的工作时间间隔按起点排序,并且不需要创建与总范围一样大的列表。

with open("0.txt") as f:
    t=[x.rstrip("\n").split("\t") for x in f.readlines()]
    intervals=[(int(x[0]),int(x[1])) for x in t]

def find_ints(intervals, mn, mx):
    next_start = mn
    for x in intervals:
        if next_start < x[0]:
            yield next_start,x[0]
            next_start = x[1]
        elif next_start < x[1]:
            next_start = x[1]
    if next_start < mx:
        yield next_start, mx

print list(find_ints(intervals, 0, 200))

输出:

(在您给出的示例中)

[(0, 1), (8, 9), (12, 20), (30, 200)]

答案 1 :(得分:1)

粗略算法:

  1. 创建一个布尔数组,所有布尔都设置为false seen = [False]*200
  2. 对输入文件进行迭代,每行start end设置seen[start] .. seen[end]True
  3. 完成后,您可以轻松地遍历数组以找到未使用的间隔。
  4. 就优化而言,如果输入范围列表按起始编号排序,那么您可以跟踪看到的最高数字,并在处理范围时使用它来过滤范围 - 例如

    之类的东西
    for (start,end) in input:
      if end<=lowest_unseen:
        next
      if start<lowest_unseen:
        start=lowest_unseen
      ...
    

    (忽略原始排序的成本)应该使整个事物O(n) - 你通过数组一次标记看/看不见,一次输出看不见。

    似乎我感觉很好。这是(未经优化的)代码,假设您的输入文件名为input

    seen = [False]*200
    file = open('input','r')
    rows = file.readlines()
    for row in rows:
      (start,end) = row.split(' ')
      print "%s %s" % (start,end)
      for x in range( int(start)-1, int(end)-1 ):
        seen[x] = True
    
    print seen[0:10]
    
    in_unseen_block=False
    start=1
    for x in range(1,200):
      val=seen[x-1]
      if val and not in_unseen_block:
        continue
      if not val and in_unseen_block:
        continue
      # Must be at a change point.
      if val:
        # we have reached the end of the block
        print "%s %s" % (start,x)
        in_unseen_block = False
      else:
        # start of new block
        start = x
        in_unseen_block = True
    # Handle end block
    if in_unseen_block:
      print "%s %s" % (start, 200)
    

    我将优化作为读者的练习。

答案 2 :(得分:1)

如果您每次打开或关闭某个输入间隔时都做笔记,则可以将openscloses的键放在一起,按顺序排序,你可以基本上思考,“好吧,让我们说每对相邻的数字形成一个区间。然后我可以把我所有的逻辑集中在这些区间作为离散的块。”

myRange = range(201)
intervals = [(1,5), (2,8), (9,12), (20,30)]
opens = {}
closes = {}

def open(index):
    if index not in opens:
        opens[index] = 0
    opens[index] += 1

def close(index):
    if index not in closes:
        closes[index] = 0
    closes[index] += 1

for start, end in intervals:
    if end > start: # Making sure to exclude empty intervals, which can be problematic later
        open(start)
        close(end)

# Sort all the interval-endpoints that we really need to look at
oset = {0:None, 200:None}
for k in opens.keys():
    oset[k] = None
for k in closes.keys():
    oset[k] = None
relevant_indices = sorted(oset.keys())

# Find the clear ranges
state = 0
results = []
for i in range(len(relevant_indices) - 1):
    start = relevant_indices[i]
    end = relevant_indices[i+1]

    start_state = state
    if start in opens:
        start_state += opens[start]
    if start in closes:
        start_state -= closes[start]

    end_state = start_state
    if end in opens:
        end_state += opens[end]
    if end in closes:
        end_state -= closes[end]
    state = end_state

    if start_state == 0:
        result_start = start
        result_end = end
        results.append((result_start, result_end))

for start, end in results:
    print(str(start) + " " + str(end))

输出:

0 1
8 9
12 20
30 200

不需要对间隔进行排序。

答案 3 :(得分:1)

这个问题似乎与Merging intervals in Python重复。

如果我很清楚这个问题,你有一个间隔列表(1 5; 2 8; 9 12; 20 30)和一个范围(0 200),你想要获得你的间隔之外的位置,但是里面给定范围。正确?

有一个Python库可以帮助你:python-intervals(也可以使用pip从PyPI获得)。免责声明:我是该库的维护者。

假设您按如下方式导入此库:

import intervals as I

很容易得到答案。基本上,您首先要根据您提供的间隔创建间隔的分离:

inters = I.closed(1, 5) | I.closed(2, 8) | I.closed(9, 12) | I.closed(20, 30)

然后你计算这些区间的补集,得到“外面”的所有东西:

compl = ~inters

然后用[0,200]创建联合,因为你想将点数限制在那个区间:

print(compl & I.closed(0, 200))

这导致:

[0,1) | (8,9) | (12,20) | (30,200]