Basic test of the rangeset data structure
=========================================

Basic construction of rangesets for single ranges:

    >>> from rangeset import *
    >>> RangeSet(1, 2)
    <RangeSet 1 -- 2>
    >>> RangeSet(NEGATIVE_INFINITY, 0)
    <RangeSet -inf -- 0>
    >>> RangeSet(0, INFINITY)
    <RangeSet 0 -- inf>
    >>> RangeSet(NEGATIVE_INFINITY, INFINITY)
    <RangeSet -inf -- inf>
    >>> RangeSet(0, 0)
    <RangeSet 0 -- 0>

Static Methods
=========================

The static methods operate on individual range sets to create
more interesting ones:

    >>> RangeSet.mutual_union(RangeSet(-10, -5), RangeSet(5, 10))
    <RangeSet -10 -- -5, 5 -- 10>
    >>> RangeSet.mutual_union(RangeSet(-10, -5), RangeSet(0, INFINITY))
    <RangeSet -10 -- -5, 0 -- inf>
    >>> RangeSet.mutual_overlaps(RangeSet(-10, 10), RangeSet(5, 20))
    <RangeSet 5 -- 10>
    >>> RangeSet.mutual_overlaps(RangeSet(-10, -5), RangeSet(-7, 5), RangeSet(0, 10))
    <RangeSet -7 -- -5, 0 -- 5>
    >>> RangeSet.mutual_overlaps(RangeSet(-10, 20), RangeSet(-8, 5), RangeSet(-4, 1))
    <RangeSet -8 -- 5>
    >>> RangeSet.mutual_overlaps(RangeSet(-10, 20), RangeSet(-8, 20), RangeSet(-5, 20))
    <RangeSet -8 -- 20>
    >>> RangeSet.mutual_overlaps(RangeSet(-10, 20), RangeSet(-8, 20), RangeSet(-5, 20), minimum=1)
    <RangeSet -10 -- 20>
    >>> RangeSet.mutual_overlaps(RangeSet(-10, 20), RangeSet(-8, 20), RangeSet(-5, 20), minimum=2)
    <RangeSet -8 -- 20>
    >>> RangeSet.mutual_overlaps(RangeSet(-10, 20), RangeSet(-8, 20), RangeSet(-5, 20), minimum=3)
    <RangeSet -5 -- 20>
    >>> RangeSet.mutual_overlaps(RangeSet(-10, 20), RangeSet(-8, 20), RangeSet(-5, 20), minimum=4)
    <RangeSet >

Logical Combinations
==========================

Instead of using the static methods above, one can construct rangesets
using logical combination of sets: inversion, union, intersection, difference, and symmetric_difference.

Inversion:

    >>> a = RangeSet(0, 1)
    >>> a.invert()
    <RangeSet -inf -- 0, 1 -- inf>
    >>> ~a
    <RangeSet -inf -- 0, 1 -- inf>
    >>> ~a.invert()
    <RangeSet 0 -- 1>
    >>> (~a.invert()) == a
    True

Union:

    >>> RangeSet(0, 1).union(RangeSet(2, 3))
    <RangeSet 0 -- 1, 2 -- 3>
    >>> a = RangeSet(0, 1) | RangeSet(2, 3)
    >>> a
    <RangeSet 0 -- 1, 2 -- 3>
    >>> ~a
    <RangeSet -inf -- 0, 1 -- 2, 3 -- inf>

Intersection:

    >>> RangeSet(0, 10).intersect(RangeSet(5, 20))
    <RangeSet 5 -- 10>
    >>> a = RangeSet(0, 10) & RangeSet(5, 20)
    >>> a
    <RangeSet 5 -- 10>
    >>> ~a
    <RangeSet -inf -- 5, 10 -- inf>
    >>> (~a) & RangeSet(0, 20)
    <RangeSet 0 -- 5, 10 -- 20>
    >>> RangeSet(0, 10) & RangeSet(5, 20) & RangeSet(2, 30)
    <RangeSet 5 -- 10>

Difference:

    >>> RangeSet(0, 100).difference(RangeSet(20, 50))
    <RangeSet 0 -- 20, 50 -- 100>
    >>> RangeSet(0, 100) - RangeSet(20, 50)
    <RangeSet 0 -- 20, 50 -- 100>

Symmetric Difference:

    >>> RangeSet(0, 50).symmetric_difference(RangeSet(20, 100))
    <RangeSet 0 -- 20, 50 -- 100>
    >>> RangeSet(0, 50) ^ RangeSet(20, 100)
    <RangeSet 0 -- 20, 50 -- 100>


Tuple shorthand
====================

Using RangeSet() for every subrange is tedious, so everything
that takes a RangeSet() will also take a tuple of (START, END).
Some examples:

    >>> RangeSet.mutual_union((-10, -5), (5, 10))
    <RangeSet -10 -- -5, 5 -- 10>
    >>> RangeSet.mutual_union((-10, -5), (0, INFINITY))
    <RangeSet -10 -- -5, 0 -- inf>
    >>> RangeSet.mutual_overlaps((-10, -5), (-7, 5), (0, 10))
    <RangeSet -7 -- -5, 0 -- 5>

    >>> RangeSet(0, 1) | (2, 3)
    <RangeSet 0 -- 1, 2 -- 3>
    >>> RangeSet(0, 2) | (3, 4) | (5, 10) | (20, 50)
    <RangeSet 0 -- 2, 3 -- 4, 5 -- 10, 20 -- 50>
    >>> (0, 1) | RangeSet(2, 3)
    <RangeSet 0 -- 1, 2 -- 3>

    >>> RangeSet(0, 20) & (5, 10)
    <RangeSet 5 -- 10>
    >>> (5, 10) & RangeSet(0, 20)
    <RangeSet 5 -- 10>
    >>> (0, 20) & RangeSet(5, 10)
    <RangeSet 5 -- 10>

    >>> (100, 0) - RangeSet(20, 50)
    <RangeSet 0 -- 20, 50 -- 100>
    >>> RangeSet(100, 0) - (20, 50)
    <RangeSet 0 -- 20, 50 -- 100>
    >>> (100, 0) - RangeSet(20, 50)
    <RangeSet 0 -- 20, 50 -- 100>
    >>> (100, 0) - (20, 50)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: unsupported operand type(s) for -: 'tuple' and 'tuple'

    >>> RangeSet(0, 100) ^ (20, 70)
    <RangeSet 0 -- 20, 70 -- 100>
    >>> (0, 100) ^ RangeSet(20, 70)
    <RangeSet 0 -- 20, 70 -- 100>
    >>> (20, 70) ^ RangeSet(0, 100) ^ (2, 30)
    <RangeSet 0 -- 2, 20 -- 30, 70 -- 100>

Properties
=====================

There are 4 interesting properties of rangesets:

- min: The minimum element
- max: The maximum element
- range(): The difference of the maximum and the minimum
- measure(): The sum of the ranges of the maximally connected ranges

    >>> a = RangeSet(0, 100)
    >>> a.max
    100
    >>> a.min
    0
    >>> a.range()
    100
    >>> a.measure()
    100
    >>> a = RangeSet(0, 20) | (40, 60)
    >>> a.range()
    60
    >>> a.measure()
    40

Note that range and measure are meaningless for unbounded ranges:

    >>> a = ~RangeSet(0, 100)
    >>> a
    <RangeSet -inf -- 0, 100 -- inf>
    >>> a.max
    inf
    >>> a.min
    -inf
    >>> a.range()
    Traceback (most recent call last):
        ...
    ValueError: Cannot compute range with unlimited bounds.
    >>> a.measure()
    Traceback (most recent call last):
        ...
    ValueError: Cannot compute range with unlimited bounds.

Membership tests
=======================

Element checking. Note that boundaries are inclusive:

    >>> 5 in RangeSet(0, 10)
    True
    >>> 0 in RangeSet(0, 10)
    True
    >>> 10 in RangeSet(0, 10)
    True
    >>> 11 in RangeSet(0, 10)
    False
    >>> -1 in RangeSet(0, 10)
    False
    >>> 0 in RangeSet(0, 0)
    True

Superset/subset checking:

    >>> (0, 10) < RangeSet(0, 10)
    False
    >>> (0, 10) <= RangeSet(0, 10)
    True
    >>> (0, 10) < RangeSet(0, 100)
    True
    >>> (20, 50) < RangeSet(0, 100)
    True
    >>> (0, 100) < RangeSet(0, 100)
    False
    >>> (0, 80) < RangeSet(0, 100)
    True
    >>> (20, 100) < RangeSet(0, 100)
    True
    >>> (0, 100) <= RangeSet(0, 100)
    True
    >>> (-10, -5) < RangeSet(0, 100)
    False
    >>> RangeSet(100, 0) > (80, 20)
    True
    >>> RangeSet(100, 0) >= (100, 0)
    True
    >>> RangeSet(100, 0) > (100, 0)
    False
    >>> RangeSet(-1000, 10000) < (NEGATIVE_INFINITY, INFINITY)
    True

Iterable
===============

Iterating over the rangeset will yield tuples for start-end of the
ranges, possibly including INFINITY or NEGATIVE_INFINITY.

    >>> a = (20, 70) ^ RangeSet(0, 100) ^ (2, 30)
    >>> list(a)
    [(0, 2), (20, 30), (70, 100)]

    >>> [x for x in ~(RangeSet(0, 1) | RangeSet(2, 3))]
    [(-inf, 0), (1, 2), (3, inf)]
