コード例 #1
0
 def test_pack_boxes_big_die_and_several_decks_of_cards(self):
     deck = ItemTuple('deck', [2, 8, 12], 0)
     die = ItemTuple('die', [8, 8, 8], 0)
     item_info = [deck, deck, deck, deck, die]
     box_dims = [8, 8, 12]
     packed_items = pack_boxes(box_dims, item_info)
     self.assertEqual(len(packed_items), 2)
     self.assertEqual(packed_items, [[deck] * 4, [die]])
コード例 #2
0
 def test_pack_boxes_three_item_one_box(self):
     '''
     test odd sized items will be rotated to fit
     '''
     item1 = ItemTuple('Item1', [13, 13, 31], 0)
     item2 = ItemTuple('Item2', [8, 13, 31], 0)
     item3 = ItemTuple('Item3', [5, 13, 31], 0)
     item_info = [item1, item2, item3]
     box_dims = [13, 26, 31]
     packed_items = pack_boxes(box_dims, item_info)
     self.assertEqual(packed_items, ([[item1, item2, item3]]))
コード例 #3
0
    def test_pack_boxes_odd_sizes(self):
        '''
        test odd sized items will be rotated to fit
        '''
        item1 = ItemTuple('Item1', [3, 8, 10], 0)
        item2 = ItemTuple('Item2', [1, 2, 5], 0)
        item3 = ItemTuple('Item3', [1, 2, 2], 0)
        item_info = [item1, item2, item2, item3]
        box_dims = [10, 20, 20]

        packed_items = pack_boxes(box_dims, item_info)
        self.assertEqual([[item1, item2, item2, item3]], packed_items)
コード例 #4
0
    def test_pack_boxes_odd_sizes_again(self):
        '''
        test items with different dimensions will be rotated to fit into one box
        '''
        item1 = ItemTuple('Item1', [1, 18, 19], 0)
        item2 = ItemTuple('Item2', [17, 18, 18], 0)
        item3 = ItemTuple('Item3', [1, 17, 18], 0)
        item_info = [item1, item2, item3]

        box_dims = [18, 18, 19]

        packed_items = pack_boxes(box_dims, item_info)
        self.assertEqual(packed_items, ([[item1, item2, item3]]))
コード例 #5
0
 def test_pack_boxes_flat_box(self):
     item = ItemTuple('MFuelMock', [1.25, 7, 10], 0)
     item_info = [item] * 3
     box_dims = [3.5, 9.5, 12.5]
     packed_items = pack_boxes(box_dims, item_info)
     self.assertEqual(2, len(packed_items))
     self.assertEqual(2, len(packed_items[0]))
コード例 #6
0
def how_many_items_fit(item_info, box_info, max_packed=None):
    '''
    returns the number of of items of a certain size can fit in a box, as well
        as the remaining volume
    assumes item and box dimensions are on in the same units

    Args:

        item_info (Dict[{
                width: float
                height: float
                length: float
                weight: float
            }])
        box_info (Dict[{
                width: float
                height: float
                length: float
                weight: float
            }])
        max_packed (int)

    Returns:
        Dict[{
            total_packed: int
            remaining_volume: float
        }]
    '''
    item_dims = sorted(
        [item_info['width'], item_info['height'], item_info['length']])
    box_dims = sorted(
        [box_info['width'], box_info['height'], box_info['length']])
    remaining_dimensions = [box_dims]
    remaining_volume = volume(box_dims)
    item = ItemTuple(None, item_dims, item_info.get('weight', 0))
    # a list of lists. each nested list is representative of a package
    items_packed = [[]]
    while remaining_dimensions != []:
        for block in remaining_dimensions:
            # items_to_pack is of length 4 at every loop because
            # insert_items_into_dimensions will pack up to 3 items at any given
            # time and then check that there are more items to pack before
            # continuing
            items_to_pack = [item, item, item, item]
            remaining_dimensions, items_packed = insert_items_into_dimensions(
                remaining_dimensions, items_to_pack, items_packed)
            # items_to_pack updates, insert items into dimensions may pack more
            # than one item and therefore we find the difference between the
            # length of the remaining items to pack and the original (4)
            remaining_volume -= volume(item_dims) * (4 - len(items_to_pack))
            if (max_packed is not None
                    and len(items_packed[0]) == int(max_packed)):
                # set remaining dimensions to empty to break from the while loop
                remaining_dimensions = []
                break
    return {
        'total_packed': len(items_packed[0]),
        'remaining_volume': remaining_volume
    }
コード例 #7
0
    def test_setup_packages(self):
        item = ItemTuple('Item1', [1, 2, 3], 0)
        normal_box = self.make_generic_box('NORM')

        packed_boxes = {normal_box: [[item]]}
        box_dictionary = setup_packages(packed_boxes)
        expected_return = Packaging(box=normal_box,
                                    items_per_box=[[item]],
                                    last_parcel=None)
        self.assertEqual(expected_return, box_dictionary)
コード例 #8
0
    def test_pack_boxes_two_item_two_box(self):
        '''
        test two items of same size as box will go into two boxes
        '''
        item = ItemTuple('Item1', [13, 13, 31], 0)
        item_info = [item, item]
        box_dims = [13, 13, 31]

        packed_items = pack_boxes(box_dims, item_info)
        self.assertEqual(packed_items, ([[item], [item]]))
コード例 #9
0
    def test_pack_boxes_one_item(self):
        '''
        test exact fit one item
        '''
        item1 = ItemTuple('Item1', [13, 13, 31], 0)
        item_info = [item1]
        box_dims = [13, 13, 31]

        packed_items = pack_boxes(box_dims, item_info)
        self.assertEqual(packed_items, ([[item1]]))
コード例 #10
0
    def test_pack_boxes_one_overflow(self):
        '''
        tests when there should be one overflow box
        '''
        item = ItemTuple('Item1', [1, 1, 1], 0)
        item_info = [item] * 28
        box_dims = [3, 3, 3]

        packed_items = pack_boxes(box_dims, item_info)
        self.assertEqual([[item] * 27, [item]], packed_items)
コード例 #11
0
    def test_pack_boxes_two_item_exact(self):
        '''
        test items will go exactly into box
        '''
        item1 = ItemTuple('Item1', [13, 13, 31], 0)
        item_info = [item1, item1]
        box_dims = [13, 26, 31]

        packed_items = pack_boxes(box_dims, item_info)
        self.assertEqual(packed_items, ([[item1, item1]]))
コード例 #12
0
 def test_pack_boxes_tight_fit_many_oblong_inexact(self):
     '''
     tests that the algorithm remains at least as accurate as it already is
     if it were perfect, the first box would have 48 in it
     '''
     item = ItemTuple('Item1', [1, 2, 3], 0)
     item_info = [item] * 49
     box_dims = [4, 8, 9]
     packed_items = pack_boxes(box_dims, item_info)
     self.assertEqual(2, len(packed_items))
     self.assertGreaterEqual(44, len(packed_items[0]))
コード例 #13
0
    def test_pack_boxes_100_items(self):
        '''
        test many items into one box with inexact fit
        '''
        item = ItemTuple('Item1', [5, 5, 5], 0)
        item_info = [item] * 100

        box_dims = [51, 51, 6]
        packed_items = pack_boxes(box_dims, item_info)

        self.assertEqual(len(packed_items), 1)
コード例 #14
0
 def test_pack_boxes_tight_fit_many_oblong(self):
     '''
     tests a tight fit for non-cubic items
     '''
     item = ItemTuple('Item1', [1, 2, 3], 0)
     item_info = [item] * 107
     box_dims = [8, 9, 9]
     packed_items = pack_boxes(box_dims, item_info)
     expected_return = [[item] * 106, [item]]
     self.assertEqual(2, len(packed_items))
     self.assertEqual(expected_return, packed_items)
コード例 #15
0
    def test_pack_boxes_dim_over_2(self):
        '''
        test that when length of item <= length of box / 2 it packs along
        longer edge
        '''
        item = ItemTuple('Item1', [3, 4, 5], 0)
        item_info = [item] * 4

        box_dims = [6, 8, 10]

        packed_items = pack_boxes(box_dims, item_info)
        self.assertEqual(packed_items, ([[item, item, item, item]]))
コード例 #16
0
    def test_pack_3_boxes(self):
        '''
        test that multiple parcels will be selected
        '''
        item = ItemTuple('Item1', [4, 4, 12], 0)
        item_info = [item] * 3
        # item_info = [['Item1', [2, 2, 12]]] * 3  # no error
        # item_info = [['Item1', [4, 4, 12]]] * 2  # no error
        box_dims = [4, 4, 12]

        packed_items = pack_boxes(box_dims, item_info)
        self.assertEqual(packed_items, ([[item], [item], [item]]))
コード例 #17
0
    def test_slightly_larger_box(self):
        '''
        test inexact dimensions
        '''
        # Fails due to recursion limits reached
        item = ItemTuple('Item1', [4, 4, 12], 0)
        item_info = [item] * 2
        box_dims = [5, 8, 12]
        # box_dims = [4, 8, 12]  # passes

        packed_items = pack_boxes(box_dims, item_info)
        self.assertEqual(packed_items, ([[item, item]]))
コード例 #18
0
 def test_setup_packages_three_flat_rates(self):
     '''
     assert that when there are two flat rates that require a different
     number of boxes, it selects the one that requires the fewest
     '''
     item = ItemTuple('Item1', [1, 2, 3], 0)
     normal_box = self.make_generic_box('NORM')
     packed_boxes = {normal_box: [[item, item]]}
     box_dictionary = setup_packages(packed_boxes)
     expected_return = Packaging(box=normal_box,
                                 items_per_box=[[item, item]],
                                 last_parcel=None)
     self.assertEqual(expected_return, box_dictionary)
コード例 #19
0
 def test_setup_packages_smaller_volume(self):
     '''
     asserts the the dictionary uses the smallest box with the fewest parcels
     '''
     item = ItemTuple('Item1', [1, 2, 3], 0)
     normal_box = self.make_generic_box('NORM')
     bigger_box = self.make_generic_box('Big', volume=2000)
     packed_boxes = {normal_box: [[item, item]], bigger_box: [[item, item]]}
     box_dictionary = setup_packages(packed_boxes)
     expected_return = Packaging(box=normal_box,
                                 items_per_box=[[item, item]],
                                 last_parcel=None)
     self.assertEqual(expected_return, box_dictionary)
コード例 #20
0
    def test_pack_boxes_100_items_2_boxes(self):
        '''
        test many items separated into 2 boxes with exact fit
        '''
        item = ItemTuple('Item1', [5, 5, 5], 0)
        item_info = [item] * 100

        box_dims = [10, 25, 25]
        packed_items = pack_boxes(box_dims, item_info)

        self.assertEqual(len(packed_items), 2)
        self.assertEqual(len(packed_items[0]), 50)
        self.assertEqual(len(packed_items[1]), 50)
コード例 #21
0
def compare_pyshipping_with_shotput():
    from random import randint
    from pyshipping import binpack_simple as binpack
    from pyshipping.package import Package
    from time import time
    items = []
    py_items = []
    box_dims = sorted(
        [randint(100, 200),
         randint(100, 200),
         randint(100, 200)])
    num_items = 500
    for _ in xrange(num_items):
        item_dims = sorted(
            [randint(20, 100),
             randint(20, 100),
             randint(20, 100)])
        items.append(ItemTuple(str(volume(item_dims)), item_dims, 0))
        py_items.append(Package((item_dims[0], item_dims[1], item_dims[2]), 0))
    start = time()
    items_packed = pack_boxes(box_dims, items)
    end = time()
    shotput = {
        'num_parcels': len(items_packed),
        'items_per_parcel': [len(parcel) for parcel in items_packed],
        'time': end - start
    }
    py_box = Package((box_dims[0], box_dims[1], box_dims[2]), 0)
    start = time()
    py_items_packed = binpack.packit(py_box, py_items)
    end = time()
    pyshipping = {
        'num_parcels': len(py_items_packed[0]),
        'items_per_parcel': [len(parcel) for parcel in py_items_packed[0]],
        'time': end - start
    }
    if len(items_packed) > len(py_items_packed[0]):
        best_results = 'pyshipping'
    elif len(items_packed) < len(py_items_packed[0]):
        best_results = 'shotput'
    else:
        best_results = 'tie'
    return {
        'shotput': shotput,
        'pyshipping': pyshipping,
        'best_results': best_results
    }
コード例 #22
0
    def test_setup_packages_fewest_parcels(self):
        '''
        asserts the the dictionary uses the smallest box with the fewest parcels
        and if flat rate has more parcels, don't even return it.
        '''
        item = ItemTuple('Item1', [1, 2, 3], 0)
        normal_box = self.make_generic_box('NORM')
        smaller_box = self.make_generic_box('Small', volume=500)

        packed_boxes = {
            normal_box: [[item, item]],
            smaller_box: [[item], [item]]
        }
        box_dictionary = setup_packages(packed_boxes)
        expected_return = Packaging(box=normal_box,
                                    items_per_box=[[item, item]],
                                    last_parcel=None)
        self.assertEqual(expected_return, box_dictionary)
コード例 #23
0
 def test_setup_packages_complex(self):
     '''
     asserts that in complex situations, it chooses the smallest,
     fewest parcels, cheapest box.
     '''
     item = ItemTuple('Item1', [1, 2, 3], 0)
     normal_box = self.make_generic_box('NORM')
     smaller_box = self.make_generic_box('Small', volume=500)
     bigger_box = self.make_generic_box('Big', volume=2000)
     packed_boxes = {
         normal_box: [[item, item]],
         bigger_box: [[item, item]],
         smaller_box: [[item], [item]]
     }
     expected_return = Packaging(box=normal_box,
                                 items_per_box=[[item, item]],
                                 last_parcel=None)
     box_dictionary = setup_packages(packed_boxes)
     self.assertEqual(expected_return, box_dictionary)
コード例 #24
0
def api_packing_algorithm(boxes_info, items_info, options):
    '''
    non-database calling method which allows checking multiple boxes
    for packing efficiency

    Args:
        session (sqlalchemy.orm.session.Session)
        boxes_info (List[Dict(
                weight: float
                height: float
                length: float
                width: float
                dimension_units: ('inches', 'centimeters', 'feet', 'meters')
                weight_units: ('grams', 'pounds', 'kilograms', 'onces')
                name: String
            )])
        items_info (List[Dict(
                weight: float
                height: float
                length: float
                width: float
                dimension_units: ('inches', 'centimeters', 'feet', 'meters')
                weight_units: ('grams', 'pounds', 'kilograms', 'onces')
                product_name: String
            )])
        options (Dict(
                max_weight: float
            ))

    Returns:
        Dict[
            'package_contents': List[Dict[
                items_packed: Dict[item, quantity]
                total_weight: float
                'best_box': Dict[
                    weight: float
                    height: float
                    length: float
                    width: float
                    dimension_units: ('inches', 'centimeters', 'feet', 'meters')
                    weight_units: ('grams', 'pounds', 'kilograms', 'onces')
                    name: String
                ]
            ]
        ]
    '''
    boxes = []
    items = []
    if len(set(box['name'] for box in boxes_info)) < len(boxes_info):
        # non-unique names for the boxes have been used.
        raise BoxError('Please use unique boxes with unique names')
    min_box_dimensions = [0, 0, 0]
    for item in items_info:
        dimensions = sorted([
            float(item['width']),
            float(item['height']),
            float(item['length'])
        ])
        weight_units = item['weight_units']
        item_weight = float(item['weight'])
        items += ([ItemTuple(item['product_name'], dimensions, item_weight)] *
                  item['quantity'])
        min_box_dimensions = [
            max(a, b) for a, b in zip(dimensions, min_box_dimensions)
        ]
    if options is not None:
        max_weight = int(options.get('max_weight', 31710))
    else:
        max_weight = 31710
    for box in boxes_info:
        dimension_units = box.get('dimension_units', units.CENTIMETERS)
        dimensions = sorted([box['width'], box['length'], box['height']])
        if does_it_fit(min_box_dimensions, dimensions):
            box_weight = float(box['weight'])
            boxes.append({
                'box':
                Box(name=box['name'],
                    weight=box_weight,
                    length=dimensions[0],
                    width=dimensions[1],
                    height=dimensions[2]),
                'dimensions':
                dimensions
            })
    if len(boxes) == 0:
        raise BoxError('Some of your products are too big for your boxes. '
                       'Please provide larger boxes.')
    # sort boxes by volume
    boxes = sorted(boxes, key=lambda box: volume(box['dimensions']))
    # send everything through the packing algorithm
    package_info = packing_algorithm(items, boxes, max_weight)

    package_contents_dict = [
        get_item_dictionary_from_list(parcel)
        for parcel in package_info.items_per_box
    ]
    package_contents = []
    best_box = [
        box for box in boxes_info if box['name'] == package_info.box.name
    ][0]
    if package_info.last_parcel is not None:
        last_parcel = [
            box for box in boxes_info
            if box['name'] == package_info.last_parcel.name
        ][0]
    else:
        last_parcel = None
    for i, parcel in enumerate(package_contents_dict):
        if i == len(package_contents_dict) - 1 and last_parcel is not None:
            selected_box = last_parcel
            total_weight = package_info.last_parcel.weight
        else:
            selected_box = best_box
            total_weight = package_info.box.weight
        items_packed = {}
        for item, info in parcel.items():
            items_packed[item] = info['quantity']
            total_weight += info['quantity'] * info['item'].weight
        package_contents.append({
            'packed_products': items_packed,
            'total_weight': total_weight,
            'box': selected_box
        })

    return {'packages': package_contents}
コード例 #25
0
def pre_pack_boxes(box_info, items_info, options):
    '''
    returns the packed items of one specific box based on item_info
    the item info input does not require a db call

    Args
        boxes_info (Dict[
                weight: float
                height: float
                length: float
                width: float
                dimension_units: ('inches', 'centimeters', 'feet', 'meters')
                weight_units: ('grams', 'pounds', 'kilograms', 'onces')
                name: String
            ])
        products_info (List[Dict[
                weight: float
                height: float
                length: float
                width: float
                dimension_units: ('inches', 'centimeters', 'feet', 'meters')
                weight_units: ('grams', 'pounds', 'kilograms', 'onces')
                product_name: String
            ])
        options (Dict[
                max_weight: float
            ])

    Returns
        List[Dict[{
            packed_products: Dict[item, qty],
            total_weight: float
        }]]
    '''
    dimension_units = box_info['dimension_units']
    box_dims = sorted(
        [box_info['width'], box_info['length'], box_info['height']])
    items_to_pack = []
    weight_units = box_info['weight_units']
    box_weight = box_info['weight']
    total_weight = box_weight
    max_weight = options.get('max_weight', 31710)  # given max weight or 70lbs
    for item in items_info:
        dimension_units = item['dimension_units']
        weight_units = item['weight_units']
        sorted_dims = sorted([item['height'], item['length'], item['width']])
        if not does_it_fit(sorted_dims, box_dims):
            raise BoxError('Some of your items are too big for the box you\'ve'
                           ' selected. Please select a bigger box or contact'
                           ' [email protected].')
        item['weight'] = item['weight']
        items_to_pack += [
            ItemTuple(item['product_name'], sorted_dims, int(item['weight']))
        ] * int(item['quantity'])
        total_weight += item['weight'] * int(item['quantity'])
    items_to_pack = sorted(items_to_pack,
                           key=lambda item: item.dimensions[2],
                           reverse=True)
    box_dims = sorted(box_dims)
    items_packed = pack_boxes(box_dims, items_to_pack)
    if math.ceil(float(total_weight) / max_weight) > len(items_packed):
        additional_box = []
        for items in items_packed:
            while weight_of_box_contents(items) + box_weight > max_weight:
                if (weight_of_box_contents(additional_box) + items[-1].weight
                        <= max_weight):
                    additional_box.append(items.pop())
                else:
                    items_packed.append(list(additional_box))
                    additional_box = [items.pop()]
        items_packed.append(additional_box)

    parcel_shipments = []
    for items in items_packed:
        item_qty = Counter()
        parcel_weight = box_weight
        for item in items:
            item_qty[item.item_number] += 1
            parcel_weight += item.weight
        parcel_shipments.append({
            'packed_products': dict(item_qty),
            'total_weight': parcel_weight
        })
    return parcel_shipments