Skip to content

Instantly share code, notes, and snippets.

@tharmann
Last active August 25, 2021 07:45
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tharmann/f1fd9aa1c5d24fe359491cd49b0eba6f to your computer and use it in GitHub Desktop.
Save tharmann/f1fd9aa1c5d24fe359491cd49b0eba6f to your computer and use it in GitHub Desktop.
4D Packer Idea Using Cloudstek's LAFF Packer - splits items into increasingly smaller chunks until everything fits into boxes (based on dimensions and weight)
<?php
//Uses: https://github.com/Cloudstek/php-laff
//THE doc: 'https://www.parkbeachsystems.com/images/usps/An_Efficient_Algorithm_for_3D_Rectangular_Box_Packing.pdf'
echo '<pre>';//readability
//DATA: array of defined boxes for shipping -outer and inner dimensions plus weight:
$boxes = array(
array( 'ol' => 8.125, 'ow' => 8.125, 'oh' => 4.125, 'il' => 8, 'iw' => 8, 'ih' => 4, 'max_weight' => 20 ),
array( 'ol' => 12.5, 'ow' => 9.875, 'oh' => 3.75, 'il' => 11.25, 'iw' => 9.75, 'ih' => 3.625, 'max_weight' => 10 ),
array( 'ol' => 14.25, 'ow' => 9.375, 'oh' => 9.5, 'il' => 14.125, 'iw' => 9.25, 'ih' => 9.375, 'max_weight' => 30 ),
array( 'ol' => 15.062, 'ow' => 11.25, 'oh' => 14.437, 'il' => 14.937, 'iw' => 11.125, 'ih' => 14.312, 'max_weight' => 60 ),
array( 'ol' => 14.687, 'ow' => 12.625, 'oh' => 13.625, 'il' => 14.562, 'iw' => 12.5, 'ih' => 13.5, 'max_weight' => 40 ),
array( 'ol' => 20.125, 'ow' => 11.25, 'oh' => 14.437, 'il' => 20, 'iw' => 11.125, 'ih' => 14.312, 'max_weight' => 60 ),
array( 'ol' => 11.125, 'ow' => 9.125, 'oh' => 6.125, 'il' => 11, 'iw' => 9, 'ih' => 6, 'max_weight' => 20 ),
array( 'ol' => 9.75, 'ow' => 3.75, 'oh' => 1.375, 'il' => 9.625, 'iw' => 3.625, 'ih' => 1.125, 'max_weight' => 1 ),
array( 'ol' => 10.875, 'ow' => 4.875, 'oh' => 1.375, 'il' => 10.75, 'iw' => 4.75, 'ih' => 1.125, 'max_weight' => 1 ),
array( 'ol' => 11, 'ow' => 7.25, 'oh' => 2.125, 'il' => 10.875, 'iw' => 7.25, 'ih' => 2, 'max_weight' => 2 ),
array( 'ol' => 11, 'ow' => 9.75, 'oh' => 1.375, 'il' => 10.875, 'iw' => 9.625, 'ih' => 1.125, 'max_weight' => 2 ),
array( 'ol' => 11, 'ow' => 7.25, 'oh' => 2.25, 'il' => 10.875, 'iw' => 7.125, 'ih' => 2.125, 'max_weight' => 4 ),
array( 'ol' => 11, 'ow' => 9.75, 'oh' => 2.25, 'il' => 10.875, 'iw' => 9.625, 'ih' => 2.125, 'max_weight' => 4 ),
array( 'ol' => 15.125, 'ow' => 11, 'oh' => 2.25, 'il' => 15, 'iw' => 10.875, 'ih' => 2.125, 'max_weight' => 6 ),
array( 'ol' => 18.875, 'ow' => 1, 'oh' => 2.25, 'il' => 18.75, 'iw' => 10.875, 'ih' => 2.125, 'max_weight' => 6 )
);
//DATA: array of our items (products)
$items = array(
array(
'name' => 'Product One',
'length' => 12.5,
'width' => 16,
'height' => 3,
'weight' => 5
),
array(
'name' => 'Product Two',
'length' => 10,
'width' => 10,
'height' => 5,
'weight' => 7
),
array(
'name' => 'Product Three',
'length' => 9,
'width' => 11,
'height' => 2,
'weight' => 2
),
array(
'name' => 'Product Four',
'length' => 8,
'width' => 11,
'height' => 1,
'weight' => 4
),array(
'name' => 'Product Five',
'length' => 4,
'width' => 4,
'height' => 1,
'weight' => 1
),
array(
'name' => 'Product Six',
'length' => 8,
'width' => 11,
'height' => 6,
'weight' => 2
),array(
'name' => 'Product Seven',
'length' => 9,
'width' => 10,
'height' => 4,
'weight' => 3
)
);
//required packer stuff
require_once( 'packer.php' );
$laff = new \Cloudstek\PhpLaff\Packer();
$done = FALSE;//our continue: yes | no FLAG
$chunk_size = count( $items );//first try to fit all items into a box
$divisor = 1;//this will be our incremental divisor if we need to split items into multiple boxes
$selected_boxes = array();//this will hold all of the boxes we will need when routine is done
while ( $done === FALSE ) {//try until done
$items_temp = array_chunk( $items, $chunk_size );//split the items array into variable sizes (chunks)
$results = pack_until_done( $laff, $items_temp );//hit our function below
foreach ( $results as $result ) {
$chosen_box_index = NULL;
foreach ( $boxes as $k => $box ) {
//length of requested box doesn't fit into width or length of our available boxes
if ( $result['size']['length'] > $box['il'] && $result['size']['length'] > $box['iw'] )
continue;
//width of requested box doesn't fit into width or length of our available boxes
if ( $result['size']['width'] > $box['iw'] && $result['size']['width'] > $box['il'] )
continue;
//same as above but height
if ( $result['size']['height'] > $box['ih'] )
continue;
//same as above but weight
if ( $result['weight'] > $box['max_weight'] )
continue;
//if we've made it this far we have a contender - figure cubic volume
$vol = $box['il'] * $box['iw'] * $box['ih'];
if ( $chosen_box_index ) {//we've alreay found a suitable box
$prev_vol = $boxes[$chosen_box_index]['il'] * $boxes[$chosen_box_index]['iw'] * $boxes[$chosen_box_index]['ih'];//cubic vol of rpeviously selected suitable box
if ( $vol < $prev_vol ) {//current suitable box is smaller than previous - save it
$chosen_box_index = $k;
}
} else {//save the index of suitable box
$chosen_box_index = $k;
}
}
if ( $chosen_box_index ) {//we found a box for the given set of items - save it in our array and set done flag to true
$done = TRUE;
$selected_boxes[] = array( 'box' => $boxes[$chosen_box_index], 'items' => $result['items'], 'total_weight' => $result['weight'] );
} else {
$done = FALSE;//no boxes found, break out of loop and try again
break;
}
}
if ( $done === FALSE ) {//continuing from the break - do the maths to chunk into smaller groups of items and run again
$divisor++;
$chunk_size = floor( $chunk_size / $divisor );
if ( $chunk_size === 1 ) {//we've tried everything and hit the smallest chunk size (1 item per box) - you might have products that are too big or heavy to fit into ANY defined boxes
$done = TRUE;
$no_boxes_found = TRUE;//may not need this...could become useful, however
}
}
}
if ( selected_boxes ) {
var_dump( $selected_boxes );
} else {
var_dump( $no_boxes_found );
}
echo '</pre>';//end readability
function pack_until_done( $laff, $sets_of_items ) {//function accepts a packer object and arrays of items
$total_sets = count( $sets_of_items );//how many sets are we processing?
$need_boxes = array();//storage for required box size per set
for ( $i = 0; $i < $total_sets; $i++ ) {//process all sets of items
$laff->pack( $sets_of_items[$i] );//run Gürbüz's routine
$items_count = count( $sets_of_items[$i] );//how many items in current set?
//init vars for names and weight
$item_names = array();
$items_weight = 0;
for ( $j = 0; $j < $items_count; $j++ ) {//loop through individ items to get names and total weight
$item_names[$j] = $sets_of_items[$i][$j]['name'];
$items_weight = $sets_of_items[$i][$j]['weight'] + $items_weight;
}
$need_boxes[$i] = array( 'size' => $laff->get_container_dimensions(), 'weight' => $items_weight, 'items' => $item_names );//store box dimensions, weight, and items
}
return $need_boxes;//return the data
}
?>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment