
class Food(object):
    def __init__(self, n, v, w):
        self._name = n
        self._value = v
        self._calories = w
    def getValue(self):
        return self._value
    def getCost(self):
        return self._calories
    def density(self):
        return self.getValue()/self.getCost()
    def __str__(self):
        return self._name + ': <' + str(self._value)\
                 + ', ' + str(self._calories) + '>'

def buildMenu(names, values, calories):
    """names, values, calories lists of same length.
       name a list of strings
       values and calories lists of numbers
       returns list of Foods"""
    menu = []
    for i in range(len(values)):
        menu.append(Food(names[i], values[i],
                          calories[i]))
    return menu

def greedy(items, maxCost, keyFunction):
    """Assumes items a list, maxCost >= 0,
         keyFunction maps elements of items to numbers"""
    itemsCopy = sorted(items, key = keyFunction,
                       reverse = True)
    result = []
    totalValue, totalCost = 0, 0
    for it in itemsCopy:
        if (totalCost + it.getCost()) <= maxCost:
            result.append(it)
            totalCost += it.getCost()
            totalValue += it.getValue()
    return (result, totalValue)

def testGreedy(items, constraint, metric):
    metrics = {'value': Food.getValue,'density':Food.density,
               'cost': lambda x: 1/Food.getCost(x)}
    try:
        taken, val = greedy(items, constraint, metrics[metric])
    except:
        print('Unknown metric', metric)
        return
    print('Total value of items taken =', val)
    for item in taken:
        print('   ', item)
        
def divide(x, y):
    if y == 0:
        return None
    else:
        return x/y

print(divide(2, 0))

print((lambda x, y: None if y ==0 else x/y)(2, 0))

def testGreedys(foods, maxUnits, metric = None):
    if metric == None:
        metric = input('Chose a metric (cost, value, or density): ')
    print('Use greedy by', metric, 'to allocate', maxUnits,
          'calories')
    testGreedy(foods, maxUnits, metric)

names = ['wine', 'beer', 'pizza', 'burger', 'fries',
         'cola', 'apple', 'donut', 'cake']
values = [89,90,95,100,90,79,50,10]
calories = [123,154,258,354,365,150,95,195]
foods = buildMenu(names, values, calories)

# testGreedys(foods, 750)

# for metric in ('cost', 'value', 'density'):
#     testGreedys(foods, 750, metric)

def maxVal(toConsider, avail):
    """Assumes toConsider a list of items,
          avail a weight
       Returns a tuple of the total value of a solution
          to the 0/1 knapsack problem and the items of
          that solution"""
    if toConsider == [] or avail == 0:
        result = (0, ()) #0 value, nothing taken
    elif toConsider[0].getCost() > avail: #cannot afford current item
        #Explore right branch only
        result = maxVal(toConsider[1:], avail)
    else:
        nextItem = toConsider[0]
        #Explore left branch
        withVal, withToTake = maxVal(toConsider[1:],
                                     avail - nextItem.getCost())
        withVal += nextItem.getValue()
        #Explore right branch
        withoutVal, withoutToTake = maxVal(toConsider[1:], avail)
        #Choose better branch
        if withVal > withoutVal:
            result = (withVal, withToTake + (nextItem,))
        else:
            result = (withoutVal, withoutToTake)
    return result

def testMaxVal(foods, maxUnits):
    for metric in ('cost', 'value', 'density'):
        print('Try greedy by', metric)
        testGreedy(foods, maxUnits, metric)
    print('Find an optimal answer')
    val, taken = maxVal(foods, maxUnits)
    print('Total value of items taken =', val)
    for item in taken:
        print('   ', item)
    return val

# testMaxVal(foods, 750)

timePerNode = 10**-9 #1 nano second
for numNodes in range(0, 101, 10):
    seconds = (2**(numNodes+1))*timePerNode
    years = seconds/(60*60*24*365)
    print('Time for', numNodes, 'nodes =',
          round(years, 4), 'years')