Last active
August 25, 2021 07:45
-
-
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)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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