Knapsack problem/Unbounded

From Rosetta Code
Revision as of 05:53, 3 December 2008 by rosettacode>Paddy3118 (Only one answer necessary for the task.)
Task
Knapsack problem/Unbounded
You are encouraged to solve this task according to the task description, using any language you may know.

A traveller gets diverted and has to make an unscheduled stop in what turns out to be Shangri La. Opting to leave, he is allowed to take as much as he likes of the following items, so long as it will fit in his knapsack, and he can carry it. He knows that he can carry no more than 25 'weights' in total; and that the capacity of his knapsack is 0.25 'cubic lengths'.

Looking just above the bar codes on the items he finds their weights and volumes. He digs out his recent copy of a financial paper and gets the value of each item.

ItemExplanationValue (each)weightVolume (each)
panacea (vials of)Incredible healing properties30000.30.025
ichor (ampules of)Vampires blood18000.20.015
gold (bars)shiney shiney25002.00.002
KnapsackFor the carrying of-<=25<=0.25 

He can only take whole units of any item,, but there is much more of any item than he could ever carry

How many of each item does he take to maximise the value of items he is carrying away with him?

Note:

  1. Their are four solutions that maximise the value taken. Only one need be given.


Python

<python>from itertools import product from collections import namedtuple from pprint import pprint as pp

Bounty = namedtuple('Bounty', 'name value weight volume') items = [ Bounty('panacea', 3000, 0.3, 0.025),

         Bounty('ichor',   1800,  0.2, 0.015),
         Bounty('gold',    2500,  2.0, 0.002),
         ]

sack = Bounty('sack', 0, 25.0, 0.25)

def totvalue(itemscount, items=items, sack=sack):

   """\
   Given the count of each item in the sack return -1 if they can't be carried or their total value.
   
   (also return the negative of the weight and the volume so taking the max of a series of return
   values will minimise the weight if values tie, and minimise the volume if values and weights tie).
   """
   weight = sum( n*item.weight for n, item in zip(itemscount, items) )
   volume = sum( n*item.volume for n, item in zip(itemscount, items) )
   if weight > sack.weight or volume > sack.volume:
       return -1, 0, 0
   return sum( n*item.value for n, item in zip(itemscount, items) ), -weight, -volume

def knapsack(items = items, sack = sack):

   
   
   # find max of any one item
   max1 = [ min(int(sack.weight/item.weight), int(sack.volume/item.volume))
            for item in items ]
   # Try all combinations of bounty items from 0 up to max1
   return max( product(*[range(n+1) for n in max1]), key = totvalue )

maxitems = knapsack() maxvalue, maxweight, maxvolume = totvalue(maxitems) maxweight = -maxweight; maxvolume = -maxvolume

print "The maximum value achievable (by exhaustive search) is %g" % maxvalue print " The number of %s items to achieve this is: %s, respectively" % (

   ','.join(item.name for item in items), maxitems )

print " The weight to carry is %.3g, and the volume used is %.3g" % (

   maxweight, maxvolume )
  1. debug print of sorted values
  2. max1 = [ min(int(sack.weight/item.weight), int(sack.volume/item.volume))
  3. for item in items ]
  4. pp( sorted( (totvalue(num), num) for num in product(*[range(n+1) for n in max1]) )[-20:] )

</python>

Sample output

The maximum value achievable (by exhaustive search) is 54500
  The number of panacea,ichor,gold items to achieve this is: (9, 0, 11), respectively
  The weight to carry is 24.7, and the volume used is 0.247

Dynamic Programming solution

A dynamic programming approach using a 2-dimensional table (One dimension for weight and one for volume). Because this approach requires that all weights and volumes be integer, I multiplied the weights and volumes by enough to make them integer. This algorithm takes O(wv) space and O(wvn) time, where w = weight of sack, v = volume of sack, n = number of types of items.

<python># Bounty = namedtuple('Bounty', 'name value weight volume')

  1. The above didn't work for me. I used this instead:

class Bounty:

   def __init__(self, name, value, weight, volume):
       self.name = name
       self.value = value
       self.weight = weight
       self.volume = volume

items = [ Bounty('panacea', 3000, 3, 25),

         Bounty('ichor',   1800,   2,  15),
         Bounty('gold',    2500,  20,   2),
         ]

sack = Bounty('sack', 0, 250, 250)

def totvalue(itemscount, items=items, sack=sack):

   """\
   Given the count of each item in the sack return -1 if they can't be carried or their total value.
   
   (also return the negative of the weight and the volume so taking the max of a series of return
   values will minimise the weight if values tie, and minimise the volume if values and weights tie).
   """
   weight = sum( n*item.weight for n, item in zip(itemscount, items) )
   volume = sum( n*item.volume for n, item in zip(itemscount, items) )
   if weight > sack.weight or volume > sack.volume:
       return -1, 0, 0
   return sum( n*item.value for n, item in zip(itemscount, items) ), -weight, -volume

def knapsack_dp(items = items, sack = sack):

   """
   Solves the Knapsack problem, with two sets of weights,
   using a dynamic programming approach
   """
   # (weight+1) x (volume+1) table
   # table[w][v] is the maximum value that can be achieved
   # with a sack of weight w and volume v.
   # They all start out as 0 (empty sack)
   table = [ [0] * (sack.volume+1) for i in range(sack.weight+1) ]
   
   for w in range(0, sack.weight+1):
       for v in range(0, sack.volume+1):
           # Consider the optimal solution, and consider the "last item" added
           # to the sack. Removing this item must produce an optimal solution
           # to the subproblem with the sack's weight and volume reduced by that
           # of the item. So we search through all possible "last items":
           for item in items:
               # Only consider items that would fit:
               if w >= item.weight and v >= item.volume:
                   table[w][v] = \
                       max(table[w][v],
                           # Optimal solution to subproblem + value of item:
                           table[w-item.weight][v-item.volume] + item.value)
   
   # Backtrack through matrix to re-construct optimum:
   result = [0] * len(items)
   w = sack.volume; v = sack.volume
   while table[w][v] != 0:
       # Find the last item that was added:
       i = [table[w-item.weight][v-item.volume] + item.value
            for item in items].index(table[w][v])
       # Record it in the result, and remove it:
       result[i] += 1
       w -= items[i].weight
       v -= items[i].volume
   return result

maxitems = knapsack_dp() maxvalue, maxweight, maxvolume = totvalue(maxitems) maxweight = -maxweight; maxvolume = -maxvolume

print "The maximum value achievable (by dynamic programming) is %g" % maxvalue print " The number of %s items to achieve this is: %s, respectively" % (

   ','.join(item.name for item in items), maxitems )

print " The weight to carry is %d, and the volume used is %d" % (

   maxweight, maxvolume )</python>

Sample output

The maximum value achievable (by dynamic programming) is 54500
  The number of panacea,ichor,gold items to achieve this is: [9, 0, 11], respectively
  The weight to carry is 247, and the volume used is 247