次のようなリストに範囲があります:
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
範囲の先頭で入力を並べ替え、それを繰り返し処理できると思います。各アイテムで、現在の範囲に追加されるか(開始が現在の範囲の終了よりも小さい場合)、または現在の範囲を算出して新しい範囲の蓄積を開始します。
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)]
zipを使用して、各範囲ペアのすべての開始値と終了値をグループ化できます。開始値が前の終了値よりも低い場合、重複があるため、その開始値と終了値を削除します。 intを使用して、ローとハイの各リストのどのインデックスを追跡しているのかを調べています。ローインデックスは常にハイインデックスより1つ高くなっています。
#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)]
_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)
_から新しい範囲を作成し、この新しい組み合わせ範囲をその背後にあるものに置き換えます(acc[:-1:] + [(min, max)]
)、それ以外の場合は単に新しい範囲を末尾に追加します(_acc + [el]
_)。
編集:他の回答を確認した後、最初と最後だけではなく、比較された2つの範囲の最小/最大を取るように更新
範囲を1回だけ反復することをお勧めしますが、次のように拡張されている現在の範囲をメモリに保持します。
def overlaps(r1, r2):
assert r1[0] <= r2[0], "Assume ranges sorted by first coordinate"
return (r2[0] <= r1[1] <= r2[1]) or (r1[0] <= r2[0] <= r1[1])
ranges = [(1, 50), (45, 47), (49, 70), (75, 85), (84, 88), (87, 92)]
def Fuse_ranges(ranges):
output_ranges = []
curr_r = list(ranges[0])
curr_overlap = False # Is the current range already overlapping?
# Assuming it is sorted by starting coordinate.
for r in ranges[1:]:
if overlaps(curr_r, r):
curr_overlap = True
curr_r[1] = max(curr_r[1], r[1]) # Extend the end of the current range.
else:
if curr_overlap:
output_ranges.append(curr_r)
curr_overlap = False
curr_r = list(r)
if curr_overlap:
output_ranges.append(curr_r)
return output_ranges
if __name__ == '__main__':
print(Fuse_ranges(sorted(ranges, key=lambda r: r[0])))
どの出力:
[[1, 70], [75, 92]]
私の解決策があなたの解決策よりもはるかに冗長である可能性があるかどうかはわかりません...
簡単な反復関数を次に示します。
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)]
質問:範囲内で最長の重複範囲を見つける
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
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
の使用方法については、詳細 こちら を参照してください。
セットを使用して重複を排除し、ソートされたリストを反復して使用すると、次のように動作します。
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
すでに投稿されている回答のほとんどはループを使用しています。代わりに 再帰的な解決策 を検討しましたか?
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)]