def make_interval(x, y):
    return pair(x, y)
def lower_bound(i):
    return head(i)
def upper_bound(i):
    return tail(i)
# printing the interval in one line requires some string
# manipulation: str turns a number into a string
# and the operator + can be applied to strings for
# string concatenation
def print_interval(i):
    return ("[ "  + str(lower_bound(i)) +
            " , " + str(upper_bound(i)) + " ]")
def add_interval(x, y):
    return make_interval(lower_bound(x) + lower_bound(y),
                         upper_bound(x) + upper_bound(y))
def mul_interval(x, y):
    p1 = lower_bound(x) * lower_bound(y)
    p2 = lower_bound(x) * upper_bound(y)
    p3 = upper_bound(x) * lower_bound(y)
    p4 = upper_bound(x) * upper_bound(y)
    return make_interval(min(p1, p2, p3, p4),
                         max(p1, p2, p3, p4))
def div_interval(x, y):
    return mul_interval(x, make_interval(1 / upper_bound(y),
                                         1 / lower_bound(y)))
def par1(r1, r2):
    return div_interval(mul_interval(r1, r2),
                        add_interval(r1, r2))
def par2(r1, r2):
    one = make_interval(1, 1)
    return div_interval(one,
                        add_interval(div_interval(one, r1),
                                     div_interval(one, r2)))

print(print_interval(par1(pair(4, 6), pair(7, 8)))
      +
      print_interval(par2(pair(4, 6), pair(7, 8))))

# expected: '[ 2.0 , 4.363636363636363 ][ 2.5454545454545454 , 3.428571428571429 ]'
