Repository: classickit (difficulty: medium)

Issue:
merge_intervals returns wrong results whenever the input is not already sorted by start, even though the docstring says the intervals may come in any order. Observed: merge_intervals([[5, 7], [1, 3]]) returns [[5, 7]], expected [[1, 3], [5, 7]]; the interval [1, 3] is silently swallowed. merge_intervals([[3, 5], [1, 4]]) returns [[3, 5]], expected [[1, 5]]. merge_intervals([[2, 3], [1, 2]]) returns [[2, 3]], expected [[1, 3]] (those two touch at the point 2 and must merge). On input that happens to be sorted by start everything is correct: [[1, 2], [2, 3]] gives [[1, 3]], the merely adjacent pair [[1, 2], [3, 4]] correctly stays separate, a singleton comes back unchanged, and [] gives []. The result must always be disjoint intervals sorted by start, covering exactly the same points as the input.

Source files:
core.py: """Merging of closed integer intervals.

merge_intervals(intervals) takes a list of closed integer intervals given
as [start, end] pairs with start <= end, in ANY order, and returns the
minimal list of disjoint intervals covering the same points, sorted by
start. Two intervals merge when they overlap or touch, i.e. when one
starts no later than the other ends: [1, 4] and [3, 6] merge to [1, 6],
and the touching pair [1, 2], [2, 3] merges to [1, 3] because both
contain the point 2. Intervals that are merely adjacent do not merge:
[1, 2] and [3, 4] stay separate. The empty list yields [].
"""


def merge_intervals(intervals):
    """Return the merged, disjoint intervals as [start, end] lists."""
    merged = []
    for start, end in intervals:
        if merged and start <= merged[-1][1]:
            merged[-1][1] = max(merged[-1][1], end)
        else:
            merged.append([start, end])
    return merged


Test files:
test_core.py: import random

from core import merge_intervals


def paint_merge(intervals):
    """Brute reference: paint doubled coordinates, then read off the runs.

    Doubling start and end keeps touching real intervals contiguous (they
    share an endpoint) while leaving a gap between merely adjacent integer
    intervals such as [1, 2] and [3, 4].
    """
    painted = set()
    for start, end in intervals:
        for point in range(2 * start, 2 * end + 1):
            painted.add(point)
    runs = []
    for point in sorted(painted):
        if runs and point == runs[-1][1] + 1:
            runs[-1][1] = point
        else:
            runs.append([point, point])
    return [[a // 2, b // 2] for a, b in runs]


def test_pinned_cases():
    assert merge_intervals([[5, 7], [1, 3]]) == [[1, 3], [5, 7]]
    assert merge_intervals([[3, 5], [1, 4]]) == [[1, 5]]
    assert merge_intervals([[2, 3], [1, 2]]) == [[1, 3]]
    assert merge_intervals([[4, 6], [1, 2], [5, 8], [0, 0]]) == [[0, 0], [1, 2], [4, 8]]
    assert merge_intervals([[1, 2], [2, 3]]) == [[1, 3]]
    assert merge_intervals([[1, 2], [3, 4]]) == [[1, 2], [3, 4]]
    assert merge_intervals([[4, 9]]) == [[4, 9]]
    assert merge_intervals([]) == []


def test_random_unsorted_vs_paint_brute():
    rng = random.Random(20250)
    for _ in range(350):
        n = rng.randint(0, 8)
        intervals = []
        for _ in range(n):
            start = rng.randint(0, 30)
            intervals.append([start, start + rng.randint(0, 6)])
        rng.shuffle(intervals)
        expected = paint_merge(intervals)
        assert merge_intervals([list(pair) for pair in intervals]) == expected


Patch:
--- a/core.py
+++ b/core.py
@@ -14,7 +14,7 @@
 def merge_intervals(intervals):
     """Return the merged, disjoint intervals as [start, end] lists."""
     merged = []
-    for start, end in intervals:
+    for start, end in sorted(intervals):
         if merged and start <= merged[-1][1]:
             merged[-1][1] = max(merged[-1][1], end)
         else: