【问题标题】:Python interval interesctionPython区间交集
【发布时间】:2013-08-23 09:43:14
【问题描述】:

我的问题如下:

具有间隔列表的文件:

1 5
2 8
9 12
20 30

还有一个范围

0 200

我想做这样一个交集,它将报告给定范围内我的间隔之间的位置 [start end]。

例如:

8 9
12 20
30 200

除了如何解决这个问题的任何想法之外,阅读一些关于优化的想法也很不错,因为与往常一样,输入文件会很大。

【问题讨论】:

  • 间隔排序了吗?
  • 我不明白你在问什么,但考虑到你匹配的范围只有 1 个,如果你需要扫描整个文件,O(n) 是最坏的情况。如果您可以将其保存在内存中并需要执行多个查询,那么例如在不同的二叉树中按开始和结束对它们进行排序可能会有所帮助...
  • 你不是也有 0 1 吗?
  • 将完整文件加载到内存中是可行的。为了更形象地表达我的意思。如果你拿一张纸并在上面画一条从 0 到 200 的线。现在添加到这条线的间隔:例如。 1-5、2-8、20-30。现在我想要的是在我们的 0-200 区间内有起始位置,其中没有添加的小区间,它们没有映射的地方。这有帮助吗?
  • @njzk2 这只是一个简单的例子。但在实际设置中,几乎为 0 的可能性,即某事将从 0 位置开始,但仍然可以这样

标签: python algorithm intervals


【解决方案1】:

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

代码

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

【讨论】:

  • 有一个问题:输出中的最高间隔结束于:9980032 现在为 249250622 设置了范围而不是 200。您知道发生了什么,还是需要更多信息?我传递的数据按要求格式化(起始位置的递增顺序)
  • 应该没有问题。您应该将最后一个区间设为 9980032、249250622。如果您在给出的示例中添加一个区间(例如 (5, 9980032))并使用 249250622 作为 mx
  • 我不知道,我已经把文件放在这里如果你想检查:dropbox.com/s/ftavov170n8987j/0使用的最大范围:249250622
  • 我想我发现了问题,当您读取文件时,您没有将字符串转换为 int,因此比较将按字典顺序而不是按数字进行,我添加了读取文件的代码。
【解决方案2】:

粗略算法:

  1. 创建一个布尔数组,全部设置为假seen = [False]*200
  2. 遍历输入文件,对于每一行start endseen[start] .. seen[end] 设置为True
  3. 完成后,您可以轻松地遍历数组以查找未使用的间隔。

在优化方面,如果输入范围列表按起始编号排序,那么您可以跟踪看到的最高编号并在处理范围时使用它来过滤范围 - 例如像

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)

我将优化留给读者作为练习。

【讨论】:

  • 对不起,由于我还是一个pyton新手,我在执行你的提示时确实有一些问题
  • 酷,谢谢。但是,以 249250621 作为我的范围,内存错误。猜想全部加载到内存中并不是一个好主意
【解决方案3】:

如果您在每次输入间隔打开或关闭时记下,您可以通过将 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

区间不需要排序。

【讨论】:

  • 工作得很好,但是当 249250621 作为范围传递时,内存错误。就像我说的,文件很大
  • 我会将文件和范围分成几个部分,这应该可以解决问题。
  • Irek,我做了一些修改,我认为可以解决您内存不足的问题。看看它是否适合你。
  • 太好了,运行速度非常快。乍一看似乎按预期工作。谢谢,现成的解决方案
  • 我首先注意到了不一致之处。有时它给出了正确的起点,但终点却是高出 2 倍的值
【解决方案4】:

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

如果我很好地理解了这个问题,您有一个区间列表 (1 5; 2 8; 9 12; 20 30) 和一个范围 (0 200),并且您希望获得区间之外的位置,但在区间内给定范围。对吧?

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

假设你按如下方式导入这个库:

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]

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-07-16
    • 2016-09-30
    • 1970-01-01
    • 2015-06-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多