from typing import Iterable
from typing import List


def _combine_ranges(ranges: Iterable[range]) -> List[range]:
    """
    Combine list of ranges to the most compact form.

    Simple ranges with step 1 can always be combined when their
    endpoints meet or overlap:

        >>> _combine_ranges([range(7, 10), range(1, 4),
        ...                  range(2, 6), range(3, 5)])
        [range(1, 6), range(7, 10)]
        >>> _combine_ranges([range(2, 7), range(3, 5)])
        [range(2, 7)]
        >>> _combine_ranges([range(1, 3), range(3, 5)])
        [range(1, 5)]
        >>> _combine_ranges([range(1, 3), range(4, 5)])
        [range(1, 3), range(4, 5)]
        >>> _combine_ranges([range(1, 2), range(2, 3), range(3, 4)])
        [range(1, 4)]

    If step is larger than 1, then combining can only occur when the
    ranges are in same "phase":

        >>> _combine_ranges([range(1, 8, 3), range(10, 14, 3)])
        [range(1, 14, 3)]
        >>> _combine_ranges([range(1, 8, 3), range(9, 14, 3)])
        [range(1, 8, 3), range(9, 14, 3)]

    Ranges with different step are not combined:

        >>> _combine_ranges([range(1, 8, 3), range(10, 14, 3),
        ...                  range(1, 20, 2)])
        [range(1, 20, 2), range(1, 14, 3)]

    Except if they have only a single item:

        >>> _combine_ranges([range(1, 3), range(3, 4, 3), range(4, 6)])
        [range(1, 6)]

    Empty ranges are removed:

        >>> _combine_ranges([range(1, 1), range(3, 4), range(6, 6)])
        [range(3, 4)]

    Empty input results in empty output:

        >>> _combine_ranges([])
        []
    """
    # Replace single item ranges with a range with step=1, remove
    # empty ranges and sort by step and start
    processed_ranges = sorted((
        x if len(x) != 1 else range(x.start, x.start + 1, 1)
        for x in ranges if x), key=(lambda x: (x.step, x.start, x.stop)))

    if not processed_ranges:
        return []

    result: List[range] = []
    last: range = processed_ranges[0]

    for cur in processed_ranges[1:]:
        # Enlarge the last range as long as the cur.start
        # is within it or in the edge (= within last_enlarded)
        last_enlarged = range(last.start, last.stop + last.step, last.step)
        if cur.step == last.step and cur.start in last_enlarged:
            last = range(last.start, max(last.stop, cur.stop), last.step)
        else:
            if last:
                result.append(last)
            last = cur

    if last:
        result.append(last)

    return result