blob: e28b5d9e7069ff2f83d7433b6256f159a4013eb2 [file] [log] [blame]
# Copyright 2020 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Helper functions for manupulating intervals."""
import copy
# Interval class representing [start, end) intervals.
class Interval:
def __init__(self, start, end):
self.start = start
self.end = end
def __repr__(self):
return 'Interval(%s, %s)' % (self.start, self.end)
def __eq__(self, other):
return self.start == other.start and self.end == other.end
def __ne__(self, other):
return self.start != other.start or self.end != other.end
@property
def size(self):
return max(self.end - self.start, 0)
def MergeIntervals(intervals):
"""Merges a list of intervals.
Args:
intervals: An Interval list of [start, end).
Returns:
A minimal sorted Interval list which is the union of all intervals.
"""
# Filter out empty intervals and sort.
intervals = sorted(list(filter(lambda x: x.size, intervals)),
key=lambda x: x.start)
if not intervals:
return []
# Merge intervals.
ret = [copy.copy(intervals[0])]
for interval in intervals[1:]:
if interval.start <= ret[-1].end:
ret[-1].end = max(ret[-1].end, interval.end)
else:
ret.append(copy.copy(interval))
return ret
def MergeAndExcludeIntervals(include_intervals, exclude_intervals):
"""Merges a list of intervals with some excluded intervals.
Args:
include_intervals: An Interval list of [start, end) that we want to include
in the union of intervals.
exclude_intervals: An Interval list of [start, end) that we want to exclude
from the union of intervals.
Returns:
A minimal sorted Interval list which is the union of all included intervals,
with the excluded intervals removed.
"""
include_intervals = MergeIntervals(include_intervals)
exclude_intervals = MergeIntervals(exclude_intervals)
exclude_size = len(exclude_intervals)
exclude_i = 0
ret = []
for include in include_intervals:
# [include.start, include.end) might be shortened in the following loop.
while exclude_i < exclude_size and include.size:
exclude = exclude_intervals[exclude_i]
# When [include.start, include.end) is completely ahead of
# [exclude.start, exclude.end), try the next excluded interval.
if exclude.end <= include.start:
exclude_i += 1
continue
# When [include.start, include.end) is completely behind
# [exclude.start, exclude.end), it will not be cut into multiple
# intervals again.
if include.end <= exclude.start:
break
# When [include.start, include.end) intersects with
# [exclude.start, exclude.end), cut off the interval before exclude.end
# from [include.start, include.end), which might be the whole interval.
if include.start < exclude.start:
ret.append(Interval(include.start, exclude.start))
include.start = exclude.end
if include.size:
ret.append(include)
return ret
def SplitInterval(interval, max_size):
"""Split an interval into multiple intervals with a size limit.
Args:
interval: An Interval.
max_size: Maximum interval size.
Returns:
A list of Interval where the size of each internal is not larger than
`max_size`.
"""
ret = []
while interval.size:
ret.append(
Interval(interval.start, min(interval.start + max_size, interval.end)))
interval = Interval(interval.start + max_size, interval.end)
return ret