【问题标题】:How to find range overlap in python?如何在python中找到范围重叠?
【发布时间】:2011-07-25 19:19:41
【问题描述】:

在 Python 中确定两个范围内的哪些值重叠的最佳方法是什么?

例如:

x = range(1,10)
y = range(8,20)

(The answer I am looking for would be the integers 8 and 9.)

给定一个范围 x,迭代另一个范围 y 并输出两个范围共享的所有值的最佳方法是什么?提前感谢您的帮助。

编辑:

作为后续,我意识到我还需要知道 x 是否与 y 重叠。我正在寻找一种方法来遍历范围列表并使用重叠的范围做一些额外的事情。是否有一个简单的 True/False 语句来完成此操作?

【问题讨论】:

  • 指定范围的特征(步长总是等于+1?或者可以是-2?)
  • 我的范围都在 +1 步中。

标签: python range


【解决方案1】:

如果步长始终为 +1(这是范围的默认值),则以下内容应该比将每个列表转换为集合或遍历任一列表更有效:

range(max(x[0], y[0]), min(x[-1], y[-1])+1)

【讨论】:

  • 真的,这应该是公认的答案。这是一条线,O(1) 的交点,而其他的最好是 O(n),最坏的情况是 O(n^2)。
  • 当两个范围之间没有重叠时,此解决方案不会给我们正确的输出。
  • 当然可以,如果没有重叠,则结果是预期的空范围。
  • 为什么不range(max(x.start, y.start), min(x.stop, y.stop))?需要注意的是,此解决方案仅适用于 x.step == 1 and y.step == 1
  • 正如@Pedram 提到的,即使没有重叠,这个解决方案也会输出一个范围,范围会有开始和停止,但范围实际上是空的。您可以使用len(range) 检查范围是否为空 -> 如果没有重叠,则为 0
【解决方案2】:

尝试设置交集:

x = range(1,10)
y = range(8,20)
xs = set(x)
xs.intersection(y)   

输出:

set([8, 9])

请注意,intersection 接受任何可迭代作为参数(y 不需要为操作转换为 set)。 有一个等效于intersection 方法的运算符:&,但在本例中为requires both arguments to be sets

【讨论】:

  • 我发现与运算符& 的交集更加直观。
  • @dr.bunsen 这个答案不值得所有这些赞成,也不被接受。 1)这个“解决方案”比安德鲁的解决方案慢 2.5(参见我的回答中的比较) 2)结果没有排序,因为它是一个集合;在某些情况下这可能是一个缺点
  • @eyquem 也许 OP 对简单性(你想要交集?然后你做 intersection)、可读性(这里不需要 cmets)和这种方法的通用性(它不假设两个范围的步骤 1 或相同步骤,您可以将它用于其他可迭代对象,而不仅仅是范围)。速度并不总是更重要的因素(尽管itersection 方法比 plaes & 更快,并且比列表理解(python 2.6)快得多,这两种方法仍然非常有趣,恕我直言。无论如何,我们正在谈论几个微秒来执行这里的代码)。
  • 感谢 cmets 和讨论。我最终选择这个答案是因为可读性和使用set() 删除重复的额外好处。
  • 您不希望这样做来找到range(0, 21984762198598125721534)range(21984762198598125721123, 45823914893546328945328456) 之间的交集。 Andrew Clark 给出了一个 O(1) 的解决方案,这将是可行的方法。
【解决方案3】:

您可以为此使用sets,但请注意set(list) 会从list 中删除所有重复条目:

>>> x = range(1,10)
>>> y = range(8,20)
>>> list(set(x) & set(y))
[8, 9]

【讨论】:

  • 好吧,ranges 无论如何都不包含重复项。此外,使用xrange 可以在构造过程中节省一些(可能很多,取决于范围大小)内存。
【解决方案4】:

一种选择是只使用列表推导,例如:

x = range(1,10) 
y = range(8,20) 

z = [i for i in x if i in y]
print z

【讨论】:

  • 据我所知,Python 2.x 中的 xrange.__contains__ 没有这种优化。这在 python 2.7 下很慢: rng = xrange(20, 1000000000); 10 英寸
  • '10 in rng' (False) 在 Python 2.7 下很慢,而 '100 in rng' (True) 在 Python 2.7 下很快;在 Python 3.2 下两者都很快。
【解决方案5】:

如果您正在寻找两个实值有界区间之间的重叠,那么这非常好:

def overlap(start1, end1, start2, end2):
    """how much does the range (start1, end1) overlap with (start2, end2)"""
    return max(max((end2-start1), 0) - max((end2-end1), 0) - max((start2-start1), 0), 0)

我在网上找不到这个,所以我想出了这个,我在这里发帖。

【讨论】:

  • 谁有解释这个的参考,或者更易读的版本?
【解决方案6】:

上面的答案似乎大多过于复杂。这一行在 Python3 中完美运行,将范围作为输入和输出。它还处理非法范围。要获取值,只需遍历结果(如果不是 None)。

# return overlap range for two range objects or None if no ovelap
# does not handle step!=1
def range_intersect(r1, r2):
    return range(max(r1.start,r2.start), min(r1.stop,r2.stop)) or None

【讨论】:

    【解决方案7】:

    这是 step=1 情况下(99% 的时间)的简单范围的答案,当使用集合比较长范围时,它可以快 2500 倍(当您只是想知道是否有重叠):

    x = range(1,10) 
    y = range(8,20)
    
    def range_overlapping(x, y):
        if x.start == x.stop or y.start == y.stop:
            return False
        return x.start <= y.stop and y.start <= x.stop
    
    >>> range_overlapping(x, y)
    True
    

    要查找重叠值:

    def overlap(x, y):
        if not range_overlapping(x, y):
            return set()
        return set(range(max(x.start, y.start), min(x.stop, y.stop)+1))
    

    视觉帮助:

    |  |           |    |
      |  |       |    |
    

    基准测试:

    x = range(1,10)
    y = range(8,20)
    
    In [151]: %timeit set(x).intersection(y)
    2.74 µs ± 11.2 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
    
    In [152]: %timeit range_overlapping(x, y)
    1.4 µs ± 2.91 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
    

    结论:即使是小范围,也快两倍。

    x = range(1,10000)
    y = range(50000, 500000)
    
    In [155]: %timeit set(x).intersection(y)
    43.1 ms ± 158 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
    
    In [156]: %timeit range_overlapping(x, y)
    1.75 µs ± 88.3 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
    

    结论:在这种情况下,您想使用 range_overlapping 函数,因为它快了 2500 倍(我个人的加速记录)

    【讨论】:

    • 我认为在range_overlapping 中,检查x.start &lt;= y.stop and y.start &lt;= x.stop 可能就足够了。事实上,我认为你使用的两个表达式(用or分隔)是等价的。
    【解决方案8】:

    对于“如果 x 是否与 y 重叠”:

    for a,b,c,d in ((1,10,10,14),
                    (1,10,9,14),
                    (1,10,4,14),
                    (1,10,4,10),
                    (1,10,4,9),
                    (1,10,4,7),
                    (1,10,1,7),
                    (1,10,-3,7),
                    (1,10,-3,2),
                    (1,10,-3,1),
                    (1,10,-11,-5)):
        x = range(a,b)
        y = range(c,d)
        print 'x==',x
        print 'y==',y
        b = not ((x[-1]<y[0]) or (y[-1]<x[0]))
        print '    x %s y' % ("does not overlap","   OVERLAPS  ")[b]
        print
    

    结果

    x== [1, 2, 3, 4, 5, 6, 7, 8, 9]
    y== [10, 11, 12, 13]
        x does not overlap y
    
    x== [1, 2, 3, 4, 5, 6, 7, 8, 9]
    y== [9, 10, 11, 12, 13]
        x    OVERLAPS   y
    
    x== [1, 2, 3, 4, 5, 6, 7, 8, 9]
    y== [4, 5, 6, 7, 8, 9, 10, 11, 12, 13]
        x    OVERLAPS   y
    
    x== [1, 2, 3, 4, 5, 6, 7, 8, 9]
    y== [4, 5, 6, 7, 8, 9]
        x    OVERLAPS   y
    
    x== [1, 2, 3, 4, 5, 6, 7, 8, 9]
    y== [4, 5, 6, 7, 8]
        x    OVERLAPS   y
    
    x== [1, 2, 3, 4, 5, 6, 7, 8, 9]
    y== [4, 5, 6]
        x    OVERLAPS   y
    
    x== [1, 2, 3, 4, 5, 6, 7, 8, 9]
    y== [1, 2, 3, 4, 5, 6]
        x    OVERLAPS   y
    
    x== [1, 2, 3, 4, 5, 6, 7, 8, 9]
    y== [-3, -2, -1, 0, 1, 2, 3, 4, 5, 6]
        x    OVERLAPS   y
    
    x== [1, 2, 3, 4, 5, 6, 7, 8, 9]
    y== [-3, -2, -1, 0, 1]
        x    OVERLAPS   y
    
    x== [1, 2, 3, 4, 5, 6, 7, 8, 9]
    y== [-3, -2, -1, 0]
        x does not overlap y
    
    x== [1, 2, 3, 4, 5, 6, 7, 8, 9]
    y== [-11, -10, -9, -8, -7, -6]
        x does not overlap y
    

    编辑 1

    速度比较:

    from time import clock
    
    x = range(-12,15)
    y = range(-5,3)
    te = clock()
    for i in xrange(100000):
        w = set(x).intersection(y)
    print '                     set(x).intersection(y)',clock()-te
    
    
    te = clock()
    for i in xrange(100000):
        w = range(max(x[0], y[0]), min(x[-1], y[-1])+1)
    print 'range(max(x[0], y[0]), min(x[-1], y[-1])+1)',clock()-te
    

    结果

                         set(x).intersection(y) 0.951059981087
    range(max(x[0], y[0]), min(x[-1], y[-1])+1) 0.377761978129
    

    这些执行时间的比率是 2.5

    【讨论】:

      【解决方案9】:

      如果您想通过任意步骤找到范围的重叠,您可以使用我的包https://github.com/avnr/rangeplus,它提供 与 Python range() 兼容的 Range() 类,以及包括交叉点在内的一些好东西:

      >>> from rangeplus import Range
      >>> Range(1, 100, 3) & Range(2, 100, 4)
      Range(10, 100, 12)
      >>> Range(200, -200, -7) & range(5, 80, 2)  # can intersect with Python range() too
      Range(67, 4, -14)
      

      Range() 也可以是未绑定的(当 stop 为 None 时,Range 继续到 +/-infinity):

      >>> Range(1, None, 3) & Range(3, None, 4)
      Range(7, None, 12)
      >>> Range(253, None, -3) & Range(208, 310, 5)
      Range(253, 207, -15)
      

      交集是计算的,而不是迭代的,这使得实现的效率与 Range() 的长度无关。

      【讨论】:

        【解决方案10】:

        此解决方案生成位于 O(1) 内存中任意数量的 range 对象的交集的整数。

        披露:在我尝试了其他东西之后,我从Python Chat 的用户那里得到了这个......不太优雅。

        解决方案

        def range_intersection(*ranges):
            ranges = set(ranges)  # `range` is hashable so we can easily eliminate duplicates
            if not ranges: return
            
            shortest_range = min(ranges, key=len)  # we will iterate over one, so choose the shortest one
            ranges.remove(shortest_range)          # note: `range` has a length, so we can use `len`
            
            for i in shortest_range:
                if all(i in range_ for range_ in ranges): yield i  # Finally, `range` implements `__contains__`
                                                                   # by checking if an iteger satisfies it's simple formula
        

        测试

        OP的问题

        x = range(1,10)
        y = range(8,20)
        list(range_intersection(x, y))
        
        [8, 9]
        

        我的例子

        limit = 10_000
        list(range_intersection(
            range(2, limit, 2),
            range(3, limit, 3),
            range(5, limit, 5),
            range(41, limit, 41),
        ))
        
        [1230, 2460, 3690, 4920, 6150, 7380, 8610, 9840]
        

        【讨论】:

        • 今天偶然发现了这个答案。做得很好:) +1
        【解决方案11】:

        假设您只使用范围,步长为1,您可以通过数学快速完成。

        def range_intersect(range_x,range_y):
            if len(range_x) == 0 or len(range_y) == 0:
                return []
            # find the endpoints
            x = (range_x[0], range_x[-1]) # from the first element to the last, inclusive
            y = (range_y[0], range_y[-1])
            # ensure min is before max
            # this can be excluded if the ranges must always be increasing
            x = tuple(sorted(x))
            y = tuple(sorted(y))
            # the range of the intersection is guaranteed to be from the maximum of the min values to the minimum of the max values, inclusive
            z = (max(x[0],y[0]),min(x[1],y[1]))
            if z[0] < z[1]:
                return range(z[0], z[1] + 1) # to make this an inclusive range
            else:
                return [] # no intersection
        

        在一对包含超过 10^7 个元素的范围内,这需要不到一秒钟的时间,与重叠的元素数量无关。我尝试了 10^8 左右的元素,但我的电脑冻结了一段时间。我怀疑你会处理那么长的列表。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-12-28
          • 1970-01-01
          • 2012-07-09
          • 2012-06-03
          相关资源
          最近更新 更多