from __future__ import division

############################################################
# Question 1
############################################################
def factorial(n):
    """Return n factorial (n!)"""
    f = 1
    for i in range(2,n+1):
        f *= i
    return f

# clever version using lambda form
def factorial2(n):
    """Return n factorial (n!)"""
    return reduce(lambda x,y: x*y, range(2,n+1))


############################################################
# Question 2
############################################################
# Solution using iteration:
def sum0(a):
    """Return the sum of elements in a"""
    s = 0
    for x in a:
        s = s + x
    return s

# Solution using reduce with a named combining function:
def add(x,y):
    """Return the sum of x and y"""
    return x+y

def sum(a):
    """Return the sum of elements in a"""
    return reduce(add, a)

# Solution using reduce with a lambda form:
def sum2(a):
    """Return the sum of elements in a"""
    return reduce(lambda x,y: x+y, a)

############################################################
# Question 3
############################################################
def average(a):
    """Return the average of the elements in a"""
    return sum(a)/len(a)

############################################################
# Question 4
############################################################
# Solution using iteration:
def to_str0(a):
    """Convert a to a string

    Convert each element in a to a string and return a single
    string obtained by concatenating all the resulting strings
    """
    s = ""
    for x in a:
        s = s + str(x)
    return s

# Solution using reduce
def to_str(a):
    """Convert a to a string

    Convert each element in a to a string and return a single
    string obtained by concatenating all the resulting strings
    """
    return reduce(lambda x,y: str(x)+str(y), a)

############################################################
# Question 5
############################################################
# Solution using iteration:
def threshold0(a, x):
    """Return the elements of a that are greater than or equal to x"""
    b = []
    for y in a:
        if y >= x: 
            b.append(y)
    return b

# Solution using filter:
def threshold1(a, x):
    """Return the elements of a that are greater than or equal to x"""
    return filter(lambda y: y >= x, a)

# Solution using list comprehension
def threshold(a, x):
    """Return the elements of a that are greater than or equal to x"""
    return [y for y in a if y >= x]

############################################################
# Question 6
############################################################
# Solution using iteration:
def square0(a):
    """Return a new array that squares each element of a"""
    b = []
    for y in a:
        b.append(a**2)
    return b

# Solution using map:
def square1(a):
    """Return a new array that squares each element of a"""
    return map(lambda x: x**2, a)

# Solution using list comprehension:
def square(a):
    """Return a new array that squares each element of a"""
    return [ x**2 for x in a ]


############################################################
# Question 7
############################################################
def is_zero(x):
    """Return an approxmate string representation of x"""
    if x == 0:
        return "zero"
    return "not zero"

############################################################
# Question 8
############################################################
def bmi_cat(bmi):
    """Return the category (a string) of a bmi value"""
    if bmi < 18.5:
        cat = "underweight"
    elif bmi < 25:
        cat = "normal"
    elif bmi < 30:
        cat = "overweight"
    else:
        cat = "obese"
    return cat

############################################################
# Question 9
############################################################
def bmi(w,h):
    """Compute the BMI given a weight and height

    Return the BMI of an individual whose weighs w pounds and
    is h inches tall.  Details on BMI calculations can be found
    here: http://en.wikipedia.org/wiki/Body_mass_index"""
    return (w/(h**2))*703

def bmi_app():
    """A simple application for computing BMI

    Prompt the user for their weight and height and report their BMI as well
    as their BMI category."""
    w = int(raw_input("Enter your weight in pounds: "))
    h = int(raw_input("Enter your height in inches: "))
    b = bmi(w,h)
    cat = bmi_cat(b)
    print "Your BMI is %f. You are %s." % (b, cat)
    


############################################################
# Question 10
############################################################
def size_format(b):
    """Return an approximate string representation of b bytes"""
    if b < 1000:
        out = "%dB" % b
    elif b < 1000000:
        out = "%.1fKB" % (b/1000)
    elif b < 1000000000:
        out = "%.1fMB" % (b/1000000)
    elif b < 1000000000000:
        out = "%.1fGB" % (b/1000000000)
    else:
        out = "%.1fTB" % (b/1000000000000)
    return out

# Clever solution
from math import log, floor
def size_format2(b):
    """Return an approximate string representation of b bytes"""
    units = ['B', 'KB', 'MB', 'GB', 'TB']
    u = min(floor(log(b, 1000)), 4)
    return "%.1f%s" % (b/(1000**u), units[u])


############################################################
# Question 11
############################################################
def nice_hour(h):
    """Convert an integer hour into an equivalent string"""
    if h == 12: return "noon"
    names = [ "midnight", "one", "two", "three", "four", "five", 
              "six", "seven", "eight", "nine", "ten", "eleven" ]
    return names[h%12]

def time_format(h,m):
    """Return an approximate string representation of a time"""
    m = int(5*round(m / 5)) 
    names = {
        0: "%s o'clock" % nice_hour(h),
        5: "five after %s" % nice_hour(h),
       10: "ten after %s" % nice_hour(h),
       15: "a quarter after %s" % nice_hour(h),
       20: "twenty after %s" % nice_hour(h), 
       25: "twenty five after %s" % nice_hour(h), 
       30: "half past %s" % nice_hour(h), 
       35: "twenty five to %s" % nice_hour(h+1), 
       40: "twenty to %s" % nice_hour(h+1), 
       45: "a quarter to %s" % nice_hour(h+1), 
       50: "ten to %s" % nice_hour(h+1), 
       55: "five to %s" % nice_hour(h+1), 
       60: "%s o'clock" % nice_hour(h+1)
    }
    return names[m]

############################################################
# Question 12
############################################################
import time

def now():
    """Return an approximate string representation of current time"""
    t = time.localtime()
    h = t.tm_hour
    m = t.tm_min
    return time_format(h, m)

