0

I am trying to implement the logic in python. Given a set of intervals, find the interval which has the maximum number of intersections. If input (1,6) (2,3) (4,11), then (1,6) should be returned. This has been answered in below but I have been unable to implement it in python. given-a-set-of-intervals-find-the-interval-which-has-the-maximum-number-of-inte.

So far I am using the below logic. Any help will be greatly appreciated.

def interval_intersection(intervals):
    if len(intervals) ==1:
        return intervals
    intervals.sort(key=lambda x: x[0])
    
    res =[intervals[0]]
    for i in range(1,len(intervals)):
        if intervals[i][0] > res[-1][1]:
            res.append(intervals[i])
        else:
            res[-1] = [min(res[-1][0],intervals[i][0]),max(res[-1][1],intervals[i][1])]
    return res

Examples:

  1. [[1,5],[5,10],[5,5]] ans should be [5,5] In case of tie [5,5] the interval with least number of elements . Here [5,5] has only 1 element in the range ie 5 hence the ans
  2. [[1,2],[3,5]] no intersection return -1
pikas
  • 83
  • 7
  • Can you explain what do you mean by `maximum` interactions in your example? Why it's not (3, 11) or (3, 4)? A few more examples would be helpful. – Daniel Hao Oct 03 '21 at 16:46
  • Yes give an example test case pls. – adarsh Oct 03 '21 at 16:48
  • *if input (1,6) (2,3) (4,11), then (1,6) should be returned* -- why? – balderman Oct 03 '21 at 16:52
  • 1
    Seems he wants initial interval with the most number of intersections with other intervals, rather than new interval covered by max number of source intervals – MBo Oct 03 '21 at 16:56
  • @balderman I want the interval with most number of intersection with other interval as staed by MBo. Here is one another example . `[[1,5],[5,10],[5,5]]` ans should be `[5,5]` – pikas Oct 03 '21 at 17:04
  • @adarsh Added few examples – pikas Oct 03 '21 at 17:07
  • @DanielHao I need the interval which intersects most with other intervals. – pikas Oct 03 '21 at 17:09
  • In the example `[[1,5],[5,10],[5,5]]`, every interval intersects two other intervals. What is the other criteria you're using to tiebreak? – kcsquared Oct 03 '21 at 17:17
  • @kcsquared in case of tie `[5,5]` the interval with least number of elements . Here `[5,5]` has only 1 element in the range ie 5 hence the ans – pikas Oct 03 '21 at 17:26
  • 1
    Alright, so if those are tied as well, is any interval fine? For example, `[1,2], [2,3]`? If not, please try to state all of the rules that you're using. Also, is your question about how to implement the pseudocode in the other post into Python, or how to modify your logic to solve the problem? – kcsquared Oct 03 '21 at 17:35
  • @kcsquared As a base I want to implement the pseudocode in the other post in python. – pikas Oct 03 '21 at 17:41

2 Answers2

2

This is a fairly straightforward implementation of David Eisenstat's algorithm. The only subtleties are:

  1. I assume that all intervals are closed on both ends, which means that sorting events should put starts before ends if they're simultaneous. If you want intervals that are fully open, or open on the right side, this order needs to be reversed.
  2. The returned interval has the most intersections, with ties broken first by smallest length, then by earliest start.
def interval_solve(intervals: Sequence[Sequence[int]]) -> Union[Sequence[int], int]:
    start_type = -1  # Assumes all intervals are closed
    end_type = 1

    events = [(s, start_type, i) for i, (s, e) in enumerate(intervals)]
    events.extend((e, end_type, i) for i, (s, e) in enumerate(intervals))
    events.sort()

    inter_count: Dict[int, int] = {}

    start_count = 0
    stop_count = 0

    for event_time, event_type, event_id in events:
        if event_type == start_type:
            start_count += 1
            inter_count[event_id] = -(stop_count + 1)
        else:
            stop_count += 1
            inter_count[event_id] += start_count

    # Find max by most intersections, then by shortest interval, then by earliest start
    answer = max(range(len(intervals)),
                 key=lambda j: (inter_count[j], intervals[j][0] - intervals[j][1]))
    if inter_count[answer] == 0:
        return -1
    return intervals[answer]
kcsquared
  • 5,244
  • 1
  • 11
  • 36
1

The actual idea is pretty simple, we sort the intervals and store some of them with an index and a boolean key for indicating the start or end events.

Then, we just traverse it while counting the end events before an index and the start events. For any index i, interval overlap count is simply, number of start events before - number of end events before (-1).

Finally, we can just check which one has the minimum length in case of multiple solutions.

# https://stackoverflow.com/questions/69426852/max-interval-intersection-point

def max_interval_count(intervals):
    interval_sorted = []
    for idx, interval in enumerate(intervals):
        s, e = interval
        interval_sorted.append([s, idx, 0]) # 0 for start
        interval_sorted.append([e, idx, 1]) # 1 for end
    interval_sorted.sort(key = lambda x: x[0])

    print(interval_sorted)

    number_of_starts = 0
    number_of_ends = 0

    overlap_count = {} 
    for event in interval_sorted:
        _, idx, start_end = event
        if start_end == 0: # start event
            # subtract all the ending before it
            overlap_count[idx] = - (number_of_ends)
            number_of_starts += 1
        else: # end event
            overlap_count[idx] += (number_of_starts - 1) # -1 as we should not include the start from the same interval
            number_of_ends += 1
    print(overlap_count)
    ans_idx = -1
    max_over_count = 0
    min_len_interval = 99999999999
    for idx, overl_cnt in overlap_count.items():
        if overl_cnt > max_over_count:
            ans_idx = idx
            max_over_count = overl_cnt
        elif overl_cnt == max_over_count and overl_cnt > 0 and (intervals[idx][1] - intervals[idx][0] + 1) < min_len_interval:
            min_len_interval = (intervals[idx][1] - intervals[idx][0] + 1)
            ans_idx = idx
    if ans_idx == -1:
        return ans_idx
    return intervals[ans_idx]


if __name__ == "__main__":
    test_1 = [[1,5],[5,10],[5,5]]
    test_2 = [[1,2],[3,5]]
    test_3 = [(1,6), (2,3), (4,11)]
    ans = max_interval_count(test_1)
    print(ans)
    print("---------")
    ans = max_interval_count(test_2)
    print(ans)
    print("---------")
    ans = max_interval_count(test_3)
    print(ans)
    print("---------")

[[1, 0, 0], [5, 0, 1], [5, 1, 0], [5, 2, 0], [5, 2, 1], [10, 1, 1]]
{0: 0, 1: 1, 2: 1}
[5, 5]
---------
[[1, 0, 0], [2, 0, 1], [3, 1, 0], [5, 1, 1]]
{0: 0, 1: 0}
-1
---------
[[1, 0, 0], [2, 1, 0], [3, 1, 1], [4, 2, 0], [6, 0, 1], [11, 2, 1]]
{0: 2, 1: 1, 2: 1}
(1, 6)
---------
Zabir Al Nazi
  • 10,298
  • 4
  • 33
  • 60
  • How are you dealing with starts and ends that overlap? In the first example, `[[1,5],[5,10],[5,5]]`, it appears that overlap counts are `{0: 0, 1: 1, 2: 1}`, which doesn't seem right-- shouldn't they all be 2? – kcsquared Oct 03 '21 at 21:14