寻找最长的重叠范围

时间:2019-12-17 14:52:58

标签: python python-3.x range

我在以下列表中有范围:

ranges = [(1, 50), (49, 70), (75, 85), (84, 88), (87, 92)]

我想找到可以由这些(当它们相互重叠时)构造的最长范围

预期输出:

[(1, 70), (75, 92)]

我有一个解决方案,但是它太复杂了,我确信必须有一个更简单的解决方案来解决这个问题

我的解决方案:

def overlap(x, y):
    return range(max(x[0], y[0]), min(x[-1], y[-1]) + 1)

ranges = [(1, 50), (49, 70), (75, 85), (84, 88), (87, 92)]

beg, end = min([x[0] for x in ranges]), 0
for i in ranges:
    if i[0] == beg:
        end = i[1]
while beg:
    for _ in ranges:
        for i in ranges:
            if i[1] > end and overlap(i, [beg, end]):
                end = i[1]
    print(beg, end)
    try:
        beg = min([x[0] for x in ranges if x[0] > end])
        for i in ranges:
            if i[0] == beg:
                end = i[1]
    except ValueError:
        beg = None

输出:

1 70
75 92

9 个答案:

答案 0 :(得分:9)

我认为您可以按范围的开头对输入进行排序,然后遍历它们。在每个项目上,它要么添加到当前范围(如果开始小于当前范围的结尾),要么我们得出当前范围并开始累积一个新范围:

def overlaps(ranges):
    ranges = sorted(ranges)  # If our inputs are garunteed sorted, we can skip this
    it = iter(ranges)
    try:
        curr_start, curr_stop = next(it)
        # overlaps = False  # If we want to exclude output ranges not produced by overlapping input ranges
    except StopIteration:
        return
    for start, stop in it:
        if curr_start <= start <= curr_stop:  # Assumes intervals are closed
            curr_stop = max(curr_stop, stop)
            # overlaps = True
        else:
            # if overlaps:
            yield curr_start, curr_stop
            curr_start, curr_stop = start, stop
            # overlaps = False
    # if overlaps:
    yield curr_start, curr_stop

print(list(overlaps([(1, 50), (49, 70), (75, 85), (84, 88), (87, 92)])))
# [(1, 70), (75, 92)]

print(list(overlaps([(20, 30), (5, 10), (1, 7), (12, 21)])))
# [(1, 10), (12, 30)]

答案 1 :(得分:6)

您可以使用zip分组每个范围对的所有开始值和结束值。如果起始值低于上一个终止值,则存在重叠,因此请删除该起始值和终止值。我们使用int跟踪每个低和高列表中的哪个索引,我们希望低索引始终比高索引高一个。


#split the numbers in to the low and high part of each range
#and set the index position for each of them
ranges = [(1, 50), (49, 70), (75, 85), (84, 88), (87, 92)]
low, high = [list(nums) for nums in zip(*ranges)] 
l, h = 1, 0

#Iterate over the ranges and remove when there is an overlap if no over lap move the pointers
while l < len(low) and h < len(high):
    if low[l] < high[h]:
        del low[l]
        del high[h]
    else:
        l +=1
        h +=1

#zip the low and high back into ranges
new_ranges = list(zip(low, high))
print(new_ranges)

输出

[(1, 70), (75, 92)]

答案 2 :(得分:4)

可以使用functools.reduce

from functools import reduce

ranges = [(1, 50), (45, 47), (49, 70), (75, 85), (84, 88), (87, 92)]

reducer = (
    lambda acc, el: acc[:-1:] + [(min(*acc[-1], *el), max(*acc[-1], *el))]
    if acc[-1][1] > el[0]
    else acc + [el]
)
print(reduce(reducer, ranges[1::], [ranges[0]]))

礼物:

[(1, 70), (75, 92)]

很难说出来,但是它使用reduce来浏览范围。如果范围中的最后一个元组与下一个提供的元组重叠(if acc[-1][1] > el[0]),它将从两者的(min, max)创建一个新范围,然后将这个新的组合范围替换为后面的范围({{1 }}),否则只需将新范围添加到末尾(acc[:-1:] + [(min, max)])。

编辑:查看其他答案后,进行更新以比较两个范围的最小值/最大值,而不仅仅是第一和最后一个

答案 3 :(得分:2)

您可以使用Counter包中的collections容器,然后对使用Counter获得的itertools个对象的组合执行设置操作。

类似的东西:

ranges = [(1, 50), (49, 70), (75, 85), (84, 88), (87, 92)]
import collections, itertools
import numpy as np

out = []
for range in ranges:
    data = np.arange(range[0], range[1]+1)
    out.append(collections.Counter(data))

for x,y in list(itertools.combinations(out, 2)): # combinations of two
    if x & y: # if they overlap
        print(x | y) # get their union

将使您获得所需的东西:

Counter({1: 1, 2: 1, 3: 1, 4: 1, 5: 1, 6: 1, 7: 1, 8: 1, 9: 1, 10: 1, 11: 1, 12: 1, 13: 1, 14: 1, 15: 1, 16: 1, 17: 1, 18: 1, 19: 1, 20: 1, 21: 1, 22: 1, 23: 1, 24: 1, 25: 1, 26: 1, 27: 1, 28: 1, 29: 1, 30: 1, 31: 1, 32: 1, 33: 1, 34: 1, 35: 1, 36: 1, 37: 1, 38: 1, 39: 1, 40: 1, 41: 1, 42: 1, 43: 1, 44: 1, 45: 1, 46: 1, 47: 1, 48: 1, 49: 1, 50: 1, 51: 1, 52: 1, 53: 1, 54: 1, 55: 1, 56: 1, 57: 1, 58: 1, 59: 1, 60: 1, 61: 1, 62: 1, 63: 1, 64: 1, 65: 1, 66: 1, 67: 1, 68: 1, 69: 1, 70: 1})
Counter({75: 1, 76: 1, 77: 1, 78: 1, 79: 1, 80: 1, 81: 1, 82: 1, 83: 1, 84: 1, 85: 1, 86: 1, 87: 1, 88: 1})
Counter({84: 1, 85: 1, 86: 1, 87: 1, 88: 1, 89: 1, 90: 1, 91: 1, 92: 1})

如果继续对多个图层执行此操作,则将获得所需的超集。您可以找到更多有关Counter的使用方法的here

答案 4 :(得分:2)

使用一个集合来消除重复项,并使用一个经过排序的列表进行迭代,下面的方法应该起作用。

代码:

ranges = [(1, 50), (49, 70), (75, 85), (84, 88), (87, 92)]

all_nums = sorted(list(set(x for r in ranges for x in range(r[0], r[1]))))

i = all_nums[0]
print(i, end=' ')
while i < all_nums[-1]:
    if i not in all_nums:
        print(i)
        i = all_nums[all_nums.index(i-1) + 1]
        print(i, end = ' ')
    i += 1
print(i+1)

输出:

ranges = [(1, 50), (49, 70), (75, 85), (84, 88), (87, 92)]

1 70
75 92


ranges = [(1, 50), (55, 70), (75, 82), (84, 88), (87, 92)]

1 50
55 70
75 82
84 92

答案 5 :(得分:2)

问题:找到范围内最长的重叠范围

ranges1 = [(1, 50), (49, 70), (75, 85), (84, 88), (87, 92)]
ranges2 = [(1, 50), (40,45), (49, 70)]


def get_overlapping(ranges):
    result = []
    start = 0
    end = ranges[0][1]

    for i, node in enumerate(ranges[1:], 1):
        if end > node[0]:
            if end < node[1]:
                end = node[1]
            continue

        result.append((start, i - 1))
        end = node[1]
        start = i

    else:
        result.append((start, i))
    return result

用法:

for _range in [ranges1, ranges2]:
    result = get_overlapping(_range)
    for o in result:
        start, end = _range[o[0]], _range[o[1]]
        print(start[0], end[1])
    print()

输出:

1 70
75 92

1 70

答案 6 :(得分:2)

我建议您仅对范围进行一次迭代,但是将当前正在扩展的范围保留在内存中,如下所示:

Checkbox

哪个输出:

import React from "react";
import ReactDOM from "react-dom";

import { createMuiTheme, ThemeProvider } from "@material-ui/core/styles";
import Checkbox from "@material-ui/core/Checkbox";

const theme = createMuiTheme({
  overrides: {
    MuiCheckbox: {
      colorSecondary: {
        color: "green",
        "&:hover": {
          color: "blue"
        },
        "&$checked": {
          color: "purple",
          "&:hover": {
            color: "lightblue"
          },
          "&.Mui-focusVisible": {
            color: "red"
          }
        },
        "&.Mui-focusVisible": {
          color: "orange"
        },
        "&.focused:not(.Mui-focusVisible):not($checked)": {
          color: "pink"
        }
      }
    }
  }
});
function App() {
  const [focused, setFocused] = React.useState(false);
  return (
    <ThemeProvider theme={theme}>
      <div className="App">
        <Checkbox
          className={focused ? "focused" : ""}
          onFocus={() => setFocused(true)}
          onBlur={() => setFocused(false)}
        />
        <input value="somewhere to move focus" />
      </div>
    </ThemeProvider>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

不确定我的解决方案是否比您的冗长得多...

答案 7 :(得分:2)

这是一个简单的迭代函数:

def merge_range(rng):
    starts, ends = [], []   
    for i, (x, y) in enumerate(rng):
        if i > 0:
            if x<= ends[-1]:
                ends[-1] = y
                continue
        starts.append(x)
        ends.append(y)
    return list(zip(starts, ends))

输出:

merge_range([(1, 50), (49, 70), (75, 85), (84, 88), (87, 92)])
# [(1, 70), (75, 92)]

merge_range([(1, 50), (49, 70), (75, 85), (84, 88), (87, 92), (99, 102), (105, 111), (150, 155), (152, 160), (154, 180)])
# [(1, 70), (75, 92), (99, 102), (105, 111), (150, 180)]

答案 8 :(得分:1)

大多数已发布的答案使用循环。您是否考虑过使用recursive solution

def merge(ranges):
  """Given a sorted list of range tuples `(a, b)` merge overlapping ranges."""

  if not(ranges):
     return [];

  if len(ranges) == 1:
    return ranges;

  a, b = ranges[0];
  c, d = ranges[1];

  # eg.: [(1, 10), (20, 30), rest]
  if b < c:
    return [(a,b)] + merge(ranges[1:]);

  # examples: [(1, 5), (2, 3), rest],
  #           [(1, 5), (2, 10), rest]
  return merge([(a, max(b, d))] + ranges[2:]);

示例

>>> merge([(1, 50), (49, 70), (75, 85), (84, 88), (87, 92)])
[(1, 70), (75, 92)]
>>> merge([(1,10), (2,3), (2,3), (8,12)])
[(1, 12)]
>>> merge (sorted([(2,5),(1,3)], key = lambda x: x[0]))
[(1, 5)]