def make_interval(x, y):
    return pair(x, y)
def lower_bound(x):
    return head(x)
def upper_bound(x):
    return tail(x)
# 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 p(n):
    return n >= 0
def n(n):
    return not p(n)
def the_trouble_maker(xl, xu, yl, yu):
    p1 = xl * yl
    p2 = xl * yu
    p3 = xu * yl
    p4 = xu * yu
    return make_interval(min(p1, p2, p3, p4),
                         max(p1, p2, p3, p4))
def mul_interval(x, y):
    xl = lower_bound(x)
    xu = upper_bound(x)
    yl = lower_bound(y)
    yu = upper_bound(y)
    return (make_interval(xl * yl, xu * yu)
            if p(xl) and p(xu) and p(yl) and p(yu)
            else make_interval(xu * yl, xu * yu)
            if p(xl) and p(xu) and n(yl) and p(yu)
            else make_interval(xu * yl, xl * yu)
            if p(xl) and p(xu) and n(yl) and n(yu)
            else make_interval(xl * yu, xu * yu)
            if n(xl) and p(xu) and p(yl) and p(yu)
            else make_interval(xu * yl, xl * yl)
            if n(xl) and p(xu) and n(yl) and n(yu)
            else make_interval(xl * yu, xu * yl)
            if n(xl) and n(xu) and p(yl) and p(yu)
            else make_interval(xl * yu, xl * yl)
            if n(xl) and n(xu) and n(yl) and p(yu)
            else make_interval(xu * yu, xl * yl)
            if n(xl) and n(xu) and n(yl) and n(yu)
            else the_trouble_maker(xl, xu, yl, yu)
            if n(xl) and p(xu) and n(yl) and p(yu)
            else error("lower larger than upper"))

print(print_interval(mul_interval(make_interval(1, 2),
                            make_interval(3, 5))))

# expected: '[ 3 , 10 ]'
