Fill order from smaller packages?

So, you need to fill the order with the packages such that the total price is maximal? This is known as Knapsack problem. In that Wikipedia article you'll find several solutions written in Python.

To be more precise, you need a solution for the unbounded knapsack problem, in contrast to popular 0/1 knapsack problem (where each item can be packed only once). Here is working code from Rosetta:

from itertools import product


NAME, SIZE, VALUE = range(3)
items = (
    # NAME, SIZE, VALUE
    ('A', 3, 5),
    ('B', 5, 9),
    ('C', 9, 16))

capacity = 13


def knapsack_unbounded_enumeration(items, C):

    # find max of any one item
    max1 = [int(C / item[SIZE]) for item in items]
    itemsizes = [item[SIZE] for item in items]
    itemvalues = [item[VALUE] for item in items]

    # def totvalue(itemscount, =itemsizes, itemvalues=itemvalues, C=C):
    def totvalue(itemscount):
        # nonlocal itemsizes, itemvalues, C

        totsize = sum(n * size for n, size in zip(itemscount, itemsizes))
        totval = sum(n * val for n, val in zip(itemscount, itemvalues))

        return (totval, -totsize) if totsize <= C else (-1, 0)

    # Try all combinations of bounty items from 0 up to max1
    bagged = max(product(*[range(n + 1) for n in max1]), key=totvalue)
    numbagged = sum(bagged)
    value, size = totvalue(bagged)
    size = -size
    # convert to (iten, count) pairs) in name order
    bagged = ['%dx%d' % (n, items[i][SIZE]) for i, n in enumerate(bagged) if n]

    return value, size, numbagged, bagged


if __name__ == '__main__':
    value, size, numbagged, bagged = knapsack_unbounded_enumeration(items, capacity)
    print(value)
    print(bagged)

Output is:

23
['1x3', '2x5']

Keep in mind that this is a NP-hard problem, so it will blow as you enter some large values :)


You can use itertools.product:

import itertools
remaining_order = 13
package_numbers = [9,5,3]
required_packages = []
a=min([x for i in range(1,remaining_order+1//min(package_numbers)) for x in itertools.product(package_numbers,repeat=i)],key=lambda x: abs(sum(x)-remaining_order))
remaining_order-=sum(a)
print(a)
print(remaining_order)

Output:

(5, 5, 3)
0

This simply does the below steps:

  1. Get value closest to 13, in the list with all the product values.

  2. Then simply make it modify the number of remaining_order.

If you want it output with 'x':

import itertools
from collections import Counter
remaining_order = 13
package_numbers = [9,5,3]
required_packages = []
a=min([x for i in range(1,remaining_order+1//min(package_numbers)) for x in itertools.product(package_numbers,repeat=i)],key=lambda x: abs(sum(x)-remaining_order))
remaining_order-=sum(a)
print(' '.join(['{0}x{1}'.format(v,k) for k,v in Counter(a).items()]))
print(remaining_order)

Output:

2x5 + 1x3
0

Tags:

Python