@@ -4,29 +4,29 @@ |
||
| 4 | 4 | * @package BoxPacker |
| 5 | 5 | * @author Doug Wright |
| 6 | 6 | */ |
| 7 | - namespace DVDoug\BoxPacker; |
|
| 7 | + namespace DVDoug\BoxPacker; |
|
| 8 | 8 | |
| 9 | - /** |
|
| 10 | - * List of boxes available to put items into, ordered by volume |
|
| 11 | - * @author Doug Wright |
|
| 12 | - * @package BoxPacker |
|
| 13 | - */ |
|
| 14 | - class BoxList extends \SplMinHeap { |
|
| 9 | + /** |
|
| 10 | + * List of boxes available to put items into, ordered by volume |
|
| 11 | + * @author Doug Wright |
|
| 12 | + * @package BoxPacker |
|
| 13 | + */ |
|
| 14 | + class BoxList extends \SplMinHeap { |
|
| 15 | 15 | |
| 16 | 16 | /** |
| 17 | 17 | * Compare elements in order to place them correctly in the heap while sifting up. |
| 18 | 18 | * @see \SplMinHeap::compare() |
| 19 | 19 | */ |
| 20 | 20 | public function compare($aBoxA, $aBoxB) { |
| 21 | - if ($aBoxB->getInnerVolume() > $aBoxA->getInnerVolume()) { |
|
| 21 | + if ($aBoxB->getInnerVolume() > $aBoxA->getInnerVolume()) { |
|
| 22 | 22 | return 1; |
| 23 | - } |
|
| 24 | - else if ($aBoxB->getInnerVolume() < $aBoxA->getInnerVolume()) { |
|
| 23 | + } |
|
| 24 | + else if ($aBoxB->getInnerVolume() < $aBoxA->getInnerVolume()) { |
|
| 25 | 25 | return -1; |
| 26 | - } |
|
| 27 | - else { |
|
| 26 | + } |
|
| 27 | + else { |
|
| 28 | 28 | return 0; |
| 29 | - } |
|
| 29 | + } |
|
| 30 | 30 | } |
| 31 | 31 | |
| 32 | - } |
|
| 32 | + } |
|
@@ -20,11 +20,9 @@ |
||
| 20 | 20 | public function compare($aBoxA, $aBoxB) { |
| 21 | 21 | if ($aBoxB->getInnerVolume() > $aBoxA->getInnerVolume()) { |
| 22 | 22 | return 1; |
| 23 | - } |
|
| 24 | - else if ($aBoxB->getInnerVolume() < $aBoxA->getInnerVolume()) { |
|
| 23 | + } else if ($aBoxB->getInnerVolume() < $aBoxA->getInnerVolume()) { |
|
| 25 | 24 | return -1; |
| 26 | - } |
|
| 27 | - else { |
|
| 25 | + } else { |
|
| 28 | 26 | return 0; |
| 29 | 27 | } |
| 30 | 28 | } |
@@ -1,17 +1,17 @@ discard block |
||
| 1 | 1 | <?php |
| 2 | 2 | /** |
| 3 | - * Box packing (3D bin packing, knapsack problem) |
|
| 4 | - * @package BoxPacker |
|
| 5 | - * @author Doug Wright |
|
| 6 | - */ |
|
| 7 | - namespace DVDoug\BoxPacker; |
|
| 8 | - |
|
| 9 | - /** |
|
| 10 | - * List of possible packed box choices, ordered by utilisation (item count, volume) |
|
| 11 | - * @author Doug Wright |
|
| 12 | - * @package BoxPacker |
|
| 13 | - */ |
|
| 14 | - class PackedBoxList extends \SplMinHeap { |
|
| 3 | + * Box packing (3D bin packing, knapsack problem) |
|
| 4 | + * @package BoxPacker |
|
| 5 | + * @author Doug Wright |
|
| 6 | + */ |
|
| 7 | + namespace DVDoug\BoxPacker; |
|
| 8 | + |
|
| 9 | + /** |
|
| 10 | + * List of possible packed box choices, ordered by utilisation (item count, volume) |
|
| 11 | + * @author Doug Wright |
|
| 12 | + * @package BoxPacker |
|
| 13 | + */ |
|
| 14 | + class PackedBoxList extends \SplMinHeap { |
|
| 15 | 15 | |
| 16 | 16 | /** |
| 17 | 17 | * Average (mean) weight of boxes |
@@ -30,14 +30,14 @@ discard block |
||
| 30 | 30 | * @see \SplMinHeap::compare() |
| 31 | 31 | */ |
| 32 | 32 | public function compare($aBoxA, $aBoxB) { |
| 33 | - $choice = $aBoxA->getItems()->count() - $aBoxB->getItems()->count(); |
|
| 34 | - if ($choice === 0) { |
|
| 33 | + $choice = $aBoxA->getItems()->count() - $aBoxB->getItems()->count(); |
|
| 34 | + if ($choice === 0) { |
|
| 35 | 35 | $choice = $aBoxB->getBox()->getInnerVolume() - $aBoxA->getBox()->getInnerVolume(); |
| 36 | - } |
|
| 37 | - if ($choice === 0) { |
|
| 36 | + } |
|
| 37 | + if ($choice === 0) { |
|
| 38 | 38 | $choice = $aBoxA->getWeight() - $aBoxB->getWeight(); |
| 39 | - } |
|
| 40 | - return $choice; |
|
| 39 | + } |
|
| 40 | + return $choice; |
|
| 41 | 41 | } |
| 42 | 42 | |
| 43 | 43 | /** |
@@ -45,14 +45,14 @@ discard block |
||
| 45 | 45 | * @return int |
| 46 | 46 | */ |
| 47 | 47 | public function reverseCompare($aBoxA, $aBoxB) { |
| 48 | - $choice = $aBoxB->getItems()->count() - $aBoxA->getItems()->count(); |
|
| 49 | - if ($choice === 0) { |
|
| 48 | + $choice = $aBoxB->getItems()->count() - $aBoxA->getItems()->count(); |
|
| 49 | + if ($choice === 0) { |
|
| 50 | 50 | $choice = $aBoxA->getBox()->getInnerVolume() - $aBoxB->getBox()->getInnerVolume(); |
| 51 | - } |
|
| 52 | - if ($choice === 0) { |
|
| 51 | + } |
|
| 52 | + if ($choice === 0) { |
|
| 53 | 53 | $choice = $aBoxB->getWeight() - $aBoxA->getWeight(); |
| 54 | - } |
|
| 55 | - return $choice; |
|
| 54 | + } |
|
| 55 | + return $choice; |
|
| 56 | 56 | } |
| 57 | 57 | |
| 58 | 58 | /** |
@@ -61,15 +61,15 @@ discard block |
||
| 61 | 61 | */ |
| 62 | 62 | public function getMeanWeight() { |
| 63 | 63 | |
| 64 | - if (!is_null($this->meanWeight)) { |
|
| 64 | + if (!is_null($this->meanWeight)) { |
|
| 65 | 65 | return $this->meanWeight; |
| 66 | - } |
|
| 66 | + } |
|
| 67 | 67 | |
| 68 | - foreach (clone $this as $box) { |
|
| 68 | + foreach (clone $this as $box) { |
|
| 69 | 69 | $this->meanWeight += $box->getWeight(); |
| 70 | - } |
|
| 70 | + } |
|
| 71 | 71 | |
| 72 | - return $this->meanWeight /= $this->count(); |
|
| 72 | + return $this->meanWeight /= $this->count(); |
|
| 73 | 73 | |
| 74 | 74 | } |
| 75 | 75 | |
@@ -79,17 +79,17 @@ discard block |
||
| 79 | 79 | */ |
| 80 | 80 | public function getWeightVariance() { |
| 81 | 81 | |
| 82 | - if (!is_null($this->weightVariance)) { |
|
| 82 | + if (!is_null($this->weightVariance)) { |
|
| 83 | 83 | return $this->weightVariance; |
| 84 | - } |
|
| 84 | + } |
|
| 85 | 85 | |
| 86 | - $mean = $this->getMeanWeight(); |
|
| 86 | + $mean = $this->getMeanWeight(); |
|
| 87 | 87 | |
| 88 | - foreach (clone $this as $box) { |
|
| 88 | + foreach (clone $this as $box) { |
|
| 89 | 89 | $this->weightVariance += pow($box->getWeight() - $mean, 2); |
| 90 | - } |
|
| 90 | + } |
|
| 91 | 91 | |
| 92 | - return $this->weightVariance /= $this->count(); |
|
| 92 | + return $this->weightVariance /= $this->count(); |
|
| 93 | 93 | |
| 94 | 94 | } |
| 95 | 95 | |
@@ -98,20 +98,20 @@ discard block |
||
| 98 | 98 | * @return float |
| 99 | 99 | */ |
| 100 | 100 | public function getVolumeUtilisation() { |
| 101 | - $itemVolume = 0; |
|
| 102 | - $boxVolume = 0; |
|
| 101 | + $itemVolume = 0; |
|
| 102 | + $boxVolume = 0; |
|
| 103 | 103 | |
| 104 | - /** @var PackedBox $box */ |
|
| 105 | - foreach (clone $this as $box) { |
|
| 104 | + /** @var PackedBox $box */ |
|
| 105 | + foreach (clone $this as $box) { |
|
| 106 | 106 | $boxVolume += $box->getBox()->getInnerVolume(); |
| 107 | 107 | |
| 108 | 108 | /** @var Item $item */ |
| 109 | 109 | foreach (clone $box->getItems() as $item ) { |
| 110 | - $itemVolume += $item->getVolume(); |
|
| 110 | + $itemVolume += $item->getVolume(); |
|
| 111 | + } |
|
| 111 | 112 | } |
| 112 | - } |
|
| 113 | 113 | |
| 114 | - return round($itemVolume / $boxVolume * 100, 1); |
|
| 114 | + return round($itemVolume / $boxVolume * 100, 1); |
|
| 115 | 115 | } |
| 116 | 116 | |
| 117 | 117 | /** |
@@ -119,9 +119,9 @@ discard block |
||
| 119 | 119 | * @param array $aBoxes |
| 120 | 120 | */ |
| 121 | 121 | public function insertFromArray(array $aBoxes) { |
| 122 | - foreach ($aBoxes as $box) { |
|
| 122 | + foreach ($aBoxes as $box) { |
|
| 123 | 123 | $this->insert($box); |
| 124 | - } |
|
| 124 | + } |
|
| 125 | 125 | } |
| 126 | 126 | |
| 127 | - } |
|
| 127 | + } |
|
@@ -106,7 +106,7 @@ |
||
| 106 | 106 | $boxVolume += $box->getBox()->getInnerVolume(); |
| 107 | 107 | |
| 108 | 108 | /** @var Item $item */ |
| 109 | - foreach (clone $box->getItems() as $item ) { |
|
| 109 | + foreach (clone $box->getItems() as $item) { |
|
| 110 | 110 | $itemVolume += $item->getVolume(); |
| 111 | 111 | } |
| 112 | 112 | } |
@@ -1,17 +1,17 @@ discard block |
||
| 1 | 1 | <?php |
| 2 | 2 | /** |
| 3 | - * Box packing (3D bin packing, knapsack problem) |
|
| 4 | - * @package BoxPacker |
|
| 5 | - * @author Doug Wright |
|
| 6 | - */ |
|
| 7 | - namespace DVDoug\BoxPacker; |
|
| 3 | + * Box packing (3D bin packing, knapsack problem) |
|
| 4 | + * @package BoxPacker |
|
| 5 | + * @author Doug Wright |
|
| 6 | + */ |
|
| 7 | + namespace DVDoug\BoxPacker; |
|
| 8 | 8 | |
| 9 | - /** |
|
| 10 | - * A "box" (or envelope?) to pack items into |
|
| 11 | - * @author Doug Wright |
|
| 12 | - * @package BoxPacker |
|
| 13 | - */ |
|
| 14 | - interface Box { |
|
| 9 | + /** |
|
| 10 | + * A "box" (or envelope?) to pack items into |
|
| 11 | + * @author Doug Wright |
|
| 12 | + * @package BoxPacker |
|
| 13 | + */ |
|
| 14 | + interface Box { |
|
| 15 | 15 | |
| 16 | 16 | /** |
| 17 | 17 | * Reference for box type (e.g. SKU or description) |
@@ -73,4 +73,4 @@ discard block |
||
| 73 | 73 | */ |
| 74 | 74 | public function getMaxWeight(); |
| 75 | 75 | |
| 76 | - } |
|
| 76 | + } |
|
@@ -1,17 +1,17 @@ discard block |
||
| 1 | 1 | <?php |
| 2 | 2 | /** |
| 3 | - * Box packing (3D bin packing, knapsack problem) |
|
| 4 | - * @package BoxPacker |
|
| 5 | - * @author Doug Wright |
|
| 6 | - */ |
|
| 7 | - namespace DVDoug\BoxPacker; |
|
| 3 | + * Box packing (3D bin packing, knapsack problem) |
|
| 4 | + * @package BoxPacker |
|
| 5 | + * @author Doug Wright |
|
| 6 | + */ |
|
| 7 | + namespace DVDoug\BoxPacker; |
|
| 8 | 8 | |
| 9 | - /** |
|
| 10 | - * An item to be packed |
|
| 11 | - * @author Doug Wright |
|
| 12 | - * @package BoxPacker |
|
| 13 | - */ |
|
| 14 | - interface Item { |
|
| 9 | + /** |
|
| 10 | + * An item to be packed |
|
| 11 | + * @author Doug Wright |
|
| 12 | + * @package BoxPacker |
|
| 13 | + */ |
|
| 14 | + interface Item { |
|
| 15 | 15 | |
| 16 | 16 | /** |
| 17 | 17 | * Item SKU etc |
@@ -49,4 +49,4 @@ discard block |
||
| 49 | 49 | */ |
| 50 | 50 | public function getVolume(); |
| 51 | 51 | |
| 52 | - } |
|
| 52 | + } |
|
@@ -1,22 +1,22 @@ discard block |
||
| 1 | 1 | <?php |
| 2 | 2 | /** |
| 3 | - * Box packing (3D bin packing, knapsack problem) |
|
| 4 | - * @package BoxPacker |
|
| 5 | - * @author Doug Wright |
|
| 6 | - */ |
|
| 7 | - namespace DVDoug\BoxPacker; |
|
| 8 | - |
|
| 9 | - use Psr\Log\LoggerAwareInterface; |
|
| 10 | - use Psr\Log\LoggerAwareTrait; |
|
| 11 | - use Psr\Log\LogLevel; |
|
| 12 | - use Psr\Log\NullLogger; |
|
| 13 | - |
|
| 14 | - /** |
|
| 15 | - * Actual packer |
|
| 16 | - * @author Doug Wright |
|
| 17 | - * @package BoxPacker |
|
| 18 | - */ |
|
| 19 | - class Packer implements LoggerAwareInterface { |
|
| 3 | + * Box packing (3D bin packing, knapsack problem) |
|
| 4 | + * @package BoxPacker |
|
| 5 | + * @author Doug Wright |
|
| 6 | + */ |
|
| 7 | + namespace DVDoug\BoxPacker; |
|
| 8 | + |
|
| 9 | + use Psr\Log\LoggerAwareInterface; |
|
| 10 | + use Psr\Log\LoggerAwareTrait; |
|
| 11 | + use Psr\Log\LogLevel; |
|
| 12 | + use Psr\Log\NullLogger; |
|
| 13 | + |
|
| 14 | + /** |
|
| 15 | + * Actual packer |
|
| 16 | + * @author Doug Wright |
|
| 17 | + * @package BoxPacker |
|
| 18 | + */ |
|
| 19 | + class Packer implements LoggerAwareInterface { |
|
| 20 | 20 | use LoggerAwareTrait; |
| 21 | 21 | |
| 22 | 22 | /** |
@@ -35,10 +35,10 @@ discard block |
||
| 35 | 35 | * Constructor |
| 36 | 36 | */ |
| 37 | 37 | public function __construct() { |
| 38 | - $this->items = new ItemList(); |
|
| 39 | - $this->boxes = new BoxList(); |
|
| 38 | + $this->items = new ItemList(); |
|
| 39 | + $this->boxes = new BoxList(); |
|
| 40 | 40 | |
| 41 | - $this->logger = new NullLogger(); |
|
| 41 | + $this->logger = new NullLogger(); |
|
| 42 | 42 | } |
| 43 | 43 | |
| 44 | 44 | /** |
@@ -47,10 +47,10 @@ discard block |
||
| 47 | 47 | * @param int $aQty |
| 48 | 48 | */ |
| 49 | 49 | public function addItem(Item $aItem, $aQty = 1) { |
| 50 | - for ($i = 0; $i < $aQty; $i++) { |
|
| 50 | + for ($i = 0; $i < $aQty; $i++) { |
|
| 51 | 51 | $this->items->insert($aItem); |
| 52 | - } |
|
| 53 | - $this->logger->log(LogLevel::INFO, "added {$aQty} x {$aItem->getDescription()}"); |
|
| 52 | + } |
|
| 53 | + $this->logger->log(LogLevel::INFO, "added {$aQty} x {$aItem->getDescription()}"); |
|
| 54 | 54 | } |
| 55 | 55 | |
| 56 | 56 | /** |
@@ -58,18 +58,18 @@ discard block |
||
| 58 | 58 | * @param \Traversable $aItems |
| 59 | 59 | */ |
| 60 | 60 | public function setItems($aItems) { |
| 61 | - if ($aItems instanceof ItemList) { |
|
| 61 | + if ($aItems instanceof ItemList) { |
|
| 62 | 62 | $this->items = clone $aItems; |
| 63 | - } |
|
| 64 | - else if (is_array($aItems)) { |
|
| 63 | + } |
|
| 64 | + else if (is_array($aItems)) { |
|
| 65 | 65 | $this->items = new ItemList(); |
| 66 | 66 | foreach ($aItems as $item) { |
| 67 | - $this->items->insert($item); |
|
| 67 | + $this->items->insert($item); |
|
| 68 | 68 | } |
| 69 | - } |
|
| 70 | - else { |
|
| 69 | + } |
|
| 70 | + else { |
|
| 71 | 71 | throw new \RuntimeException('Not a valid list of items'); |
| 72 | - } |
|
| 72 | + } |
|
| 73 | 73 | } |
| 74 | 74 | |
| 75 | 75 | /** |
@@ -77,8 +77,8 @@ discard block |
||
| 77 | 77 | * @param Box $aBox |
| 78 | 78 | */ |
| 79 | 79 | public function addBox(Box $aBox) { |
| 80 | - $this->boxes->insert($aBox); |
|
| 81 | - $this->logger->log(LogLevel::INFO, "added box {$aBox->getReference()}"); |
|
| 80 | + $this->boxes->insert($aBox); |
|
| 81 | + $this->logger->log(LogLevel::INFO, "added box {$aBox->getReference()}"); |
|
| 82 | 82 | } |
| 83 | 83 | |
| 84 | 84 | /** |
@@ -86,7 +86,7 @@ discard block |
||
| 86 | 86 | * @param BoxList $aBoxList |
| 87 | 87 | */ |
| 88 | 88 | public function setBoxes(BoxList $aBoxList) { |
| 89 | - $this->boxes = clone $aBoxList; |
|
| 89 | + $this->boxes = clone $aBoxList; |
|
| 90 | 90 | } |
| 91 | 91 | |
| 92 | 92 | /** |
@@ -96,15 +96,15 @@ discard block |
||
| 96 | 96 | * @return PackedBoxList |
| 97 | 97 | */ |
| 98 | 98 | public function pack() { |
| 99 | - $packedBoxes = $this->doVolumePacking(); |
|
| 99 | + $packedBoxes = $this->doVolumePacking(); |
|
| 100 | 100 | |
| 101 | - //If we have multiple boxes, try and optimise/even-out weight distribution |
|
| 102 | - if ($packedBoxes->count() > 1) { |
|
| 101 | + //If we have multiple boxes, try and optimise/even-out weight distribution |
|
| 102 | + if ($packedBoxes->count() > 1) { |
|
| 103 | 103 | $packedBoxes = $this->redistributeWeight($packedBoxes); |
| 104 | - } |
|
| 104 | + } |
|
| 105 | 105 | |
| 106 | - $this->logger->log(LogLevel::INFO, "packing completed, {$packedBoxes->count()} boxes"); |
|
| 107 | - return $packedBoxes; |
|
| 106 | + $this->logger->log(LogLevel::INFO, "packing completed, {$packedBoxes->count()} boxes"); |
|
| 107 | + return $packedBoxes; |
|
| 108 | 108 | } |
| 109 | 109 | |
| 110 | 110 | /** |
@@ -115,53 +115,53 @@ discard block |
||
| 115 | 115 | */ |
| 116 | 116 | public function doVolumePacking() { |
| 117 | 117 | |
| 118 | - $packedBoxes = new PackedBoxList; |
|
| 118 | + $packedBoxes = new PackedBoxList; |
|
| 119 | 119 | |
| 120 | - //Keep going until everything packed |
|
| 121 | - while ($this->items->count()) { |
|
| 120 | + //Keep going until everything packed |
|
| 121 | + while ($this->items->count()) { |
|
| 122 | 122 | $boxesToEvaluate = clone $this->boxes; |
| 123 | 123 | $packedBoxesIteration = new PackedBoxList; |
| 124 | 124 | |
| 125 | 125 | //Loop through boxes starting with smallest, see what happens |
| 126 | 126 | while (!$boxesToEvaluate->isEmpty()) { |
| 127 | - $box = $boxesToEvaluate->extract(); |
|
| 128 | - $packedBox = $this->packIntoBox($box, clone $this->items); |
|
| 129 | - if ($packedBox->getItems()->count()) { |
|
| 127 | + $box = $boxesToEvaluate->extract(); |
|
| 128 | + $packedBox = $this->packIntoBox($box, clone $this->items); |
|
| 129 | + if ($packedBox->getItems()->count()) { |
|
| 130 | 130 | $packedBoxesIteration->insert($packedBox); |
| 131 | 131 | |
| 132 | 132 | //Have we found a single box that contains everything? |
| 133 | 133 | if ($packedBox->getItems()->count() === $this->items->count()) { |
| 134 | - break; |
|
| 134 | + break; |
|
| 135 | + } |
|
| 135 | 136 | } |
| 136 | - } |
|
| 137 | 137 | } |
| 138 | 138 | |
| 139 | 139 | //Check iteration was productive |
| 140 | 140 | if ($packedBoxesIteration->isEmpty()) { |
| 141 | - throw new \RuntimeException('Item ' . $this->items->top()->getDescription() . ' is too large to fit into any box'); |
|
| 141 | + throw new \RuntimeException('Item ' . $this->items->top()->getDescription() . ' is too large to fit into any box'); |
|
| 142 | 142 | } |
| 143 | 143 | |
| 144 | 144 | //Find best box of iteration, and remove packed items from unpacked list |
| 145 | 145 | $bestBox = $packedBoxesIteration->top(); |
| 146 | 146 | $unPackedItems = $this->items->asArray(); |
| 147 | 147 | foreach(clone $bestBox->getItems() as $packedItem) { |
| 148 | - foreach ($unPackedItems as $unpackedKey => $unpackedItem) { |
|
| 148 | + foreach ($unPackedItems as $unpackedKey => $unpackedItem) { |
|
| 149 | 149 | if ($packedItem === $unpackedItem) { |
| 150 | - unset($unPackedItems[$unpackedKey]); |
|
| 151 | - break; |
|
| 150 | + unset($unPackedItems[$unpackedKey]); |
|
| 151 | + break; |
|
| 152 | + } |
|
| 152 | 153 | } |
| 153 | - } |
|
| 154 | 154 | } |
| 155 | 155 | $unpackedItemList = new ItemList(); |
| 156 | 156 | foreach ($unPackedItems as $unpackedItem) { |
| 157 | - $unpackedItemList->insert($unpackedItem); |
|
| 157 | + $unpackedItemList->insert($unpackedItem); |
|
| 158 | 158 | } |
| 159 | 159 | $this->items = $unpackedItemList; |
| 160 | 160 | $packedBoxes->insert($bestBox); |
| 161 | 161 | |
| 162 | - } |
|
| 162 | + } |
|
| 163 | 163 | |
| 164 | - return $packedBoxes; |
|
| 164 | + return $packedBoxes; |
|
| 165 | 165 | } |
| 166 | 166 | |
| 167 | 167 | /** |
@@ -172,54 +172,54 @@ discard block |
||
| 172 | 172 | */ |
| 173 | 173 | public function redistributeWeight(PackedBoxList $aPackedBoxes) { |
| 174 | 174 | |
| 175 | - $targetWeight = $aPackedBoxes->getMeanWeight(); |
|
| 176 | - $this->logger->log(LogLevel::DEBUG, "repacking for weight distribution, weight variance {$aPackedBoxes->getWeightVariance()}, target weight {$targetWeight}"); |
|
| 175 | + $targetWeight = $aPackedBoxes->getMeanWeight(); |
|
| 176 | + $this->logger->log(LogLevel::DEBUG, "repacking for weight distribution, weight variance {$aPackedBoxes->getWeightVariance()}, target weight {$targetWeight}"); |
|
| 177 | 177 | |
| 178 | - $packedBoxes = new PackedBoxList; |
|
| 178 | + $packedBoxes = new PackedBoxList; |
|
| 179 | 179 | |
| 180 | - $overWeightBoxes = []; |
|
| 181 | - $underWeightBoxes = []; |
|
| 182 | - foreach (clone $aPackedBoxes as $packedBox) { |
|
| 180 | + $overWeightBoxes = []; |
|
| 181 | + $underWeightBoxes = []; |
|
| 182 | + foreach (clone $aPackedBoxes as $packedBox) { |
|
| 183 | 183 | $boxWeight = $packedBox->getWeight(); |
| 184 | 184 | if ($boxWeight > $targetWeight) { |
| 185 | - $overWeightBoxes[] = $packedBox; |
|
| 185 | + $overWeightBoxes[] = $packedBox; |
|
| 186 | 186 | } |
| 187 | 187 | else if ($boxWeight < $targetWeight) { |
| 188 | - $underWeightBoxes[] = $packedBox; |
|
| 188 | + $underWeightBoxes[] = $packedBox; |
|
| 189 | 189 | } |
| 190 | 190 | else { |
| 191 | - $packedBoxes->insert($packedBox); //target weight, so we'll keep these |
|
| 191 | + $packedBoxes->insert($packedBox); //target weight, so we'll keep these |
|
| 192 | + } |
|
| 192 | 193 | } |
| 193 | - } |
|
| 194 | 194 | |
| 195 | - do { //Keep moving items from most overweight box to most underweight box |
|
| 195 | + do { //Keep moving items from most overweight box to most underweight box |
|
| 196 | 196 | $tryRepack = false; |
| 197 | 197 | $this->logger->log(LogLevel::DEBUG, 'boxes under/over target: ' . count($underWeightBoxes) . '/' . count($overWeightBoxes)); |
| 198 | 198 | |
| 199 | 199 | foreach ($underWeightBoxes as $u => $underWeightBox) { |
| 200 | - $this->logger->log(LogLevel::DEBUG, 'Underweight Box ' . $u); |
|
| 201 | - foreach ($overWeightBoxes as $o => $overWeightBox) { |
|
| 200 | + $this->logger->log(LogLevel::DEBUG, 'Underweight Box ' . $u); |
|
| 201 | + foreach ($overWeightBoxes as $o => $overWeightBox) { |
|
| 202 | 202 | $this->logger->log(LogLevel::DEBUG, 'Overweight Box ' . $o); |
| 203 | 203 | $overWeightBoxItems = $overWeightBox->getItems()->asArray(); |
| 204 | 204 | |
| 205 | 205 | //For each item in the heavier box, try and move it to the lighter one |
| 206 | 206 | foreach ($overWeightBoxItems as $oi => $overWeightBoxItem) { |
| 207 | - $this->logger->log(LogLevel::DEBUG, 'Overweight Item ' . $oi); |
|
| 208 | - if ($underWeightBox->getWeight() + $overWeightBoxItem->getWeight() > $targetWeight) { |
|
| 207 | + $this->logger->log(LogLevel::DEBUG, 'Overweight Item ' . $oi); |
|
| 208 | + if ($underWeightBox->getWeight() + $overWeightBoxItem->getWeight() > $targetWeight) { |
|
| 209 | 209 | $this->logger->log(LogLevel::DEBUG, 'Skipping item for hindering weight distribution'); |
| 210 | 210 | continue; //skip if moving this item would hinder rather than help weight distribution |
| 211 | - } |
|
| 211 | + } |
|
| 212 | 212 | |
| 213 | - $newItemsForLighterBox = clone $underWeightBox->getItems(); |
|
| 214 | - $newItemsForLighterBox->insert($overWeightBoxItem); |
|
| 213 | + $newItemsForLighterBox = clone $underWeightBox->getItems(); |
|
| 214 | + $newItemsForLighterBox->insert($overWeightBoxItem); |
|
| 215 | 215 | |
| 216 | - $newLighterBoxPacker = new Packer(); //we may need a bigger box |
|
| 217 | - $newLighterBoxPacker->setBoxes($this->boxes); |
|
| 218 | - $newLighterBoxPacker->setItems($newItemsForLighterBox); |
|
| 219 | - $this->logger->log(LogLevel::INFO, "[ATTEMPTING TO PACK LIGHTER BOX]"); |
|
| 220 | - $newLighterBox = $newLighterBoxPacker->doVolumePacking()->extract(); |
|
| 216 | + $newLighterBoxPacker = new Packer(); //we may need a bigger box |
|
| 217 | + $newLighterBoxPacker->setBoxes($this->boxes); |
|
| 218 | + $newLighterBoxPacker->setItems($newItemsForLighterBox); |
|
| 219 | + $this->logger->log(LogLevel::INFO, "[ATTEMPTING TO PACK LIGHTER BOX]"); |
|
| 220 | + $newLighterBox = $newLighterBoxPacker->doVolumePacking()->extract(); |
|
| 221 | 221 | |
| 222 | - if ($newLighterBox->getItems()->count() === $newItemsForLighterBox->count()) { //new item fits |
|
| 222 | + if ($newLighterBox->getItems()->count() === $newItemsForLighterBox->count()) { //new item fits |
|
| 223 | 223 | $this->logger->log(LogLevel::DEBUG, 'New item fits'); |
| 224 | 224 | unset($overWeightBoxItems[$oi]); //now packed in different box |
| 225 | 225 | |
@@ -230,8 +230,8 @@ discard block |
||
| 230 | 230 | $this->logger->log(LogLevel::INFO, "[ATTEMPTING TO PACK HEAVIER BOX]"); |
| 231 | 231 | $newHeavierBoxes = $newHeavierBoxPacker->doVolumePacking(); |
| 232 | 232 | if (count($newHeavierBoxes) > 1) { //found an edge case in packing algorithm that *increased* box count |
| 233 | - $this->logger->log(LogLevel::INFO, "[REDISTRIBUTING WEIGHT] Abandoning redistribution, because new packing is less efficient than original"); |
|
| 234 | - return $aPackedBoxes; |
|
| 233 | + $this->logger->log(LogLevel::INFO, "[REDISTRIBUTING WEIGHT] Abandoning redistribution, because new packing is less efficient than original"); |
|
| 234 | + return $aPackedBoxes; |
|
| 235 | 235 | } |
| 236 | 236 | |
| 237 | 237 | $overWeightBoxes[$o] = $newHeavierBoxes->extract(); |
@@ -241,17 +241,17 @@ discard block |
||
| 241 | 241 | usort($overWeightBoxes, [$packedBoxes, 'reverseCompare']); |
| 242 | 242 | usort($underWeightBoxes, [$packedBoxes, 'reverseCompare']); |
| 243 | 243 | break 3; |
| 244 | - } |
|
| 244 | + } |
|
| 245 | + } |
|
| 245 | 246 | } |
| 246 | - } |
|
| 247 | 247 | } |
| 248 | - } while ($tryRepack); |
|
| 248 | + } while ($tryRepack); |
|
| 249 | 249 | |
| 250 | - //Combine back into a single list |
|
| 251 | - $packedBoxes->insertFromArray($overWeightBoxes); |
|
| 252 | - $packedBoxes->insertFromArray($underWeightBoxes); |
|
| 250 | + //Combine back into a single list |
|
| 251 | + $packedBoxes->insertFromArray($overWeightBoxes); |
|
| 252 | + $packedBoxes->insertFromArray($underWeightBoxes); |
|
| 253 | 253 | |
| 254 | - return $packedBoxes; |
|
| 254 | + return $packedBoxes; |
|
| 255 | 255 | } |
| 256 | 256 | |
| 257 | 257 | |
@@ -262,22 +262,22 @@ discard block |
||
| 262 | 262 | * @return PackedBox packed box |
| 263 | 263 | */ |
| 264 | 264 | public function packIntoBox(Box $aBox, ItemList $aItems) { |
| 265 | - $this->logger->log(LogLevel::DEBUG, "[EVALUATING BOX] {$aBox->getReference()}"); |
|
| 265 | + $this->logger->log(LogLevel::DEBUG, "[EVALUATING BOX] {$aBox->getReference()}"); |
|
| 266 | 266 | |
| 267 | - $packedItems = new ItemList; |
|
| 268 | - $remainingDepth = $aBox->getInnerDepth(); |
|
| 269 | - $remainingWeight = $aBox->getMaxWeight() - $aBox->getEmptyWeight(); |
|
| 270 | - $remainingWidth = $aBox->getInnerWidth(); |
|
| 271 | - $remainingLength = $aBox->getInnerLength(); |
|
| 267 | + $packedItems = new ItemList; |
|
| 268 | + $remainingDepth = $aBox->getInnerDepth(); |
|
| 269 | + $remainingWeight = $aBox->getMaxWeight() - $aBox->getEmptyWeight(); |
|
| 270 | + $remainingWidth = $aBox->getInnerWidth(); |
|
| 271 | + $remainingLength = $aBox->getInnerLength(); |
|
| 272 | 272 | |
| 273 | - $layerWidth = $layerLength = $layerDepth = 0; |
|
| 274 | - while(!$aItems->isEmpty()) { |
|
| 273 | + $layerWidth = $layerLength = $layerDepth = 0; |
|
| 274 | + while(!$aItems->isEmpty()) { |
|
| 275 | 275 | |
| 276 | 276 | $itemToPack = $aItems->top(); |
| 277 | 277 | |
| 278 | 278 | if ($itemToPack->getDepth() > $remainingDepth || $itemToPack->getWeight() > $remainingWeight) { |
| 279 | - $aItems->extract(); |
|
| 280 | - continue; |
|
| 279 | + $aItems->extract(); |
|
| 280 | + continue; |
|
| 281 | 281 | } |
| 282 | 282 | |
| 283 | 283 | $this->logger->log(LogLevel::DEBUG, "evaluating item {$itemToPack->getDescription()}"); |
@@ -292,67 +292,67 @@ discard block |
||
| 292 | 292 | |
| 293 | 293 | if ($fitsSameGap >= 0 || $fitsRotatedGap >= 0) { |
| 294 | 294 | |
| 295 | - $packedItems->insert($aItems->extract()); |
|
| 296 | - $remainingWeight -= $itemToPack->getWeight(); |
|
| 295 | + $packedItems->insert($aItems->extract()); |
|
| 296 | + $remainingWeight -= $itemToPack->getWeight(); |
|
| 297 | 297 | |
| 298 | - if ($fitsRotatedGap < 0 || |
|
| 298 | + if ($fitsRotatedGap < 0 || |
|
| 299 | 299 | ($fitsSameGap >= 0 && $fitsSameGap <= $fitsRotatedGap) || |
| 300 | 300 | ($itemWidth <= $remainingWidth && !$aItems->isEmpty() && $aItems->top() == $itemToPack && $remainingLength >= 2 * $itemLength)) { |
| 301 | 301 | $this->logger->log(LogLevel::DEBUG, "fits (better) unrotated"); |
| 302 | 302 | $remainingLength -= $itemLength; |
| 303 | 303 | $layerLength += $itemLength; |
| 304 | 304 | $layerWidth = max($itemWidth, $layerWidth); |
| 305 | - } |
|
| 306 | - else { |
|
| 305 | + } |
|
| 306 | + else { |
|
| 307 | 307 | $this->logger->log(LogLevel::DEBUG, "fits (better) rotated"); |
| 308 | 308 | $remainingLength -= $itemWidth; |
| 309 | 309 | $layerLength += $itemWidth; |
| 310 | 310 | $layerWidth = max($itemLength, $layerWidth); |
| 311 | - } |
|
| 312 | - $layerDepth = max($layerDepth, $itemToPack->getDepth()); //greater than 0, items will always be less deep |
|
| 311 | + } |
|
| 312 | + $layerDepth = max($layerDepth, $itemToPack->getDepth()); //greater than 0, items will always be less deep |
|
| 313 | 313 | |
| 314 | - //allow items to be stacked in place within the same footprint up to current layerdepth |
|
| 315 | - $maxStackDepth = $layerDepth - $itemToPack->getDepth(); |
|
| 316 | - while(!$aItems->isEmpty()) { |
|
| 314 | + //allow items to be stacked in place within the same footprint up to current layerdepth |
|
| 315 | + $maxStackDepth = $layerDepth - $itemToPack->getDepth(); |
|
| 316 | + while(!$aItems->isEmpty()) { |
|
| 317 | 317 | $potentialStackItem = $aItems->top(); |
| 318 | 318 | if ($potentialStackItem->getDepth() <= $maxStackDepth && |
| 319 | 319 | $potentialStackItem->getWeight() <= $remainingWeight && |
| 320 | 320 | $potentialStackItem->getWidth() <= $itemToPack->getWidth() && |
| 321 | 321 | $potentialStackItem->getLength() <= $itemToPack->getLength()) { |
| 322 | - $remainingWeight -= $potentialStackItem->getWeight(); |
|
| 323 | - $maxStackDepth -= $potentialStackItem->getDepth(); |
|
| 324 | - $packedItems->insert($aItems->extract()); |
|
| 322 | + $remainingWeight -= $potentialStackItem->getWeight(); |
|
| 323 | + $maxStackDepth -= $potentialStackItem->getDepth(); |
|
| 324 | + $packedItems->insert($aItems->extract()); |
|
| 325 | 325 | } |
| 326 | 326 | else { |
| 327 | - break; |
|
| 327 | + break; |
|
| 328 | + } |
|
| 328 | 329 | } |
| 329 | - } |
|
| 330 | 330 | } |
| 331 | 331 | else { |
| 332 | - if ($remainingWidth >= min($itemWidth, $itemLength) && $layerDepth > 0 && $layerWidth > 0 && $layerLength > 0) { |
|
| 332 | + if ($remainingWidth >= min($itemWidth, $itemLength) && $layerDepth > 0 && $layerWidth > 0 && $layerLength > 0) { |
|
| 333 | 333 | $this->logger->log(LogLevel::DEBUG, "No more fit in lengthwise, resetting for new row"); |
| 334 | 334 | $remainingLength += $layerLength; |
| 335 | 335 | $remainingWidth -= $layerWidth; |
| 336 | 336 | $layerWidth = $layerLength = 0; |
| 337 | 337 | continue; |
| 338 | - } |
|
| 338 | + } |
|
| 339 | 339 | |
| 340 | - if ($remainingLength < min($itemWidth, $itemLength) || $layerDepth == 0) { |
|
| 340 | + if ($remainingLength < min($itemWidth, $itemLength) || $layerDepth == 0) { |
|
| 341 | 341 | $this->logger->log(LogLevel::DEBUG, "doesn't fit on layer even when empty"); |
| 342 | 342 | $aItems->extract(); |
| 343 | 343 | continue; |
| 344 | - } |
|
| 344 | + } |
|
| 345 | 345 | |
| 346 | - $remainingWidth = $layerWidth ? min(floor($layerWidth * 1.1), $aBox->getInnerWidth()) : $aBox->getInnerWidth(); |
|
| 347 | - $remainingLength = $layerLength ? min(floor($layerLength * 1.1), $aBox->getInnerLength()) : $aBox->getInnerLength(); |
|
| 348 | - $remainingDepth -= $layerDepth; |
|
| 346 | + $remainingWidth = $layerWidth ? min(floor($layerWidth * 1.1), $aBox->getInnerWidth()) : $aBox->getInnerWidth(); |
|
| 347 | + $remainingLength = $layerLength ? min(floor($layerLength * 1.1), $aBox->getInnerLength()) : $aBox->getInnerLength(); |
|
| 348 | + $remainingDepth -= $layerDepth; |
|
| 349 | 349 | |
| 350 | - $layerWidth = $layerLength = $layerDepth = 0; |
|
| 351 | - $this->logger->log(LogLevel::DEBUG, "doesn't fit, so starting next vertical layer"); |
|
| 350 | + $layerWidth = $layerLength = $layerDepth = 0; |
|
| 351 | + $this->logger->log(LogLevel::DEBUG, "doesn't fit, so starting next vertical layer"); |
|
| 352 | + } |
|
| 352 | 353 | } |
| 353 | - } |
|
| 354 | - $this->logger->log(LogLevel::DEBUG, "done with this box"); |
|
| 355 | - return new PackedBox($aBox, $packedItems, $remainingWidth, $remainingLength, $remainingDepth, $remainingWeight); |
|
| 354 | + $this->logger->log(LogLevel::DEBUG, "done with this box"); |
|
| 355 | + return new PackedBox($aBox, $packedItems, $remainingWidth, $remainingLength, $remainingDepth, $remainingWeight); |
|
| 356 | 356 | } |
| 357 | 357 | |
| 358 | 358 | /** |
@@ -363,7 +363,7 @@ discard block |
||
| 363 | 363 | * @return ItemList items packed into box |
| 364 | 364 | */ |
| 365 | 365 | public function packBox(Box $aBox, ItemList $aItems) { |
| 366 | - $packedBox = $this->packIntoBox($aBox, $aItems); |
|
| 367 | - return $packedBox->getItems(); |
|
| 366 | + $packedBox = $this->packIntoBox($aBox, $aItems); |
|
| 367 | + return $packedBox->getItems(); |
|
| 368 | + } |
|
| 368 | 369 | } |
| 369 | - } |
|
@@ -144,7 +144,7 @@ discard block |
||
| 144 | 144 | //Find best box of iteration, and remove packed items from unpacked list |
| 145 | 145 | $bestBox = $packedBoxesIteration->top(); |
| 146 | 146 | $unPackedItems = $this->items->asArray(); |
| 147 | - foreach(clone $bestBox->getItems() as $packedItem) { |
|
| 147 | + foreach (clone $bestBox->getItems() as $packedItem) { |
|
| 148 | 148 | foreach ($unPackedItems as $unpackedKey => $unpackedItem) { |
| 149 | 149 | if ($packedItem === $unpackedItem) { |
| 150 | 150 | unset($unPackedItems[$unpackedKey]); |
@@ -173,7 +173,7 @@ discard block |
||
| 173 | 173 | public function redistributeWeight(PackedBoxList $aPackedBoxes) { |
| 174 | 174 | |
| 175 | 175 | $targetWeight = $aPackedBoxes->getMeanWeight(); |
| 176 | - $this->logger->log(LogLevel::DEBUG, "repacking for weight distribution, weight variance {$aPackedBoxes->getWeightVariance()}, target weight {$targetWeight}"); |
|
| 176 | + $this->logger->log(LogLevel::DEBUG, "repacking for weight distribution, weight variance {$aPackedBoxes->getWeightVariance()}, target weight {$targetWeight}"); |
|
| 177 | 177 | |
| 178 | 178 | $packedBoxes = new PackedBoxList; |
| 179 | 179 | |
@@ -194,19 +194,19 @@ discard block |
||
| 194 | 194 | |
| 195 | 195 | do { //Keep moving items from most overweight box to most underweight box |
| 196 | 196 | $tryRepack = false; |
| 197 | - $this->logger->log(LogLevel::DEBUG, 'boxes under/over target: ' . count($underWeightBoxes) . '/' . count($overWeightBoxes)); |
|
| 197 | + $this->logger->log(LogLevel::DEBUG, 'boxes under/over target: ' . count($underWeightBoxes) . '/' . count($overWeightBoxes)); |
|
| 198 | 198 | |
| 199 | 199 | foreach ($underWeightBoxes as $u => $underWeightBox) { |
| 200 | - $this->logger->log(LogLevel::DEBUG, 'Underweight Box ' . $u); |
|
| 200 | + $this->logger->log(LogLevel::DEBUG, 'Underweight Box ' . $u); |
|
| 201 | 201 | foreach ($overWeightBoxes as $o => $overWeightBox) { |
| 202 | - $this->logger->log(LogLevel::DEBUG, 'Overweight Box ' . $o); |
|
| 202 | + $this->logger->log(LogLevel::DEBUG, 'Overweight Box ' . $o); |
|
| 203 | 203 | $overWeightBoxItems = $overWeightBox->getItems()->asArray(); |
| 204 | 204 | |
| 205 | 205 | //For each item in the heavier box, try and move it to the lighter one |
| 206 | 206 | foreach ($overWeightBoxItems as $oi => $overWeightBoxItem) { |
| 207 | - $this->logger->log(LogLevel::DEBUG, 'Overweight Item ' . $oi); |
|
| 207 | + $this->logger->log(LogLevel::DEBUG, 'Overweight Item ' . $oi); |
|
| 208 | 208 | if ($underWeightBox->getWeight() + $overWeightBoxItem->getWeight() > $targetWeight) { |
| 209 | - $this->logger->log(LogLevel::DEBUG, 'Skipping item for hindering weight distribution'); |
|
| 209 | + $this->logger->log(LogLevel::DEBUG, 'Skipping item for hindering weight distribution'); |
|
| 210 | 210 | continue; //skip if moving this item would hinder rather than help weight distribution |
| 211 | 211 | } |
| 212 | 212 | |
@@ -216,21 +216,21 @@ discard block |
||
| 216 | 216 | $newLighterBoxPacker = new Packer(); //we may need a bigger box |
| 217 | 217 | $newLighterBoxPacker->setBoxes($this->boxes); |
| 218 | 218 | $newLighterBoxPacker->setItems($newItemsForLighterBox); |
| 219 | - $this->logger->log(LogLevel::INFO, "[ATTEMPTING TO PACK LIGHTER BOX]"); |
|
| 219 | + $this->logger->log(LogLevel::INFO, "[ATTEMPTING TO PACK LIGHTER BOX]"); |
|
| 220 | 220 | $newLighterBox = $newLighterBoxPacker->doVolumePacking()->extract(); |
| 221 | 221 | |
| 222 | 222 | if ($newLighterBox->getItems()->count() === $newItemsForLighterBox->count()) { //new item fits |
| 223 | - $this->logger->log(LogLevel::DEBUG, 'New item fits'); |
|
| 223 | + $this->logger->log(LogLevel::DEBUG, 'New item fits'); |
|
| 224 | 224 | unset($overWeightBoxItems[$oi]); //now packed in different box |
| 225 | 225 | |
| 226 | 226 | $newHeavierBoxPacker = new Packer(); //we may be able to use a smaller box |
| 227 | 227 | $newHeavierBoxPacker->setBoxes($this->boxes); |
| 228 | 228 | $newHeavierBoxPacker->setItems($overWeightBoxItems); |
| 229 | 229 | |
| 230 | - $this->logger->log(LogLevel::INFO, "[ATTEMPTING TO PACK HEAVIER BOX]"); |
|
| 230 | + $this->logger->log(LogLevel::INFO, "[ATTEMPTING TO PACK HEAVIER BOX]"); |
|
| 231 | 231 | $newHeavierBoxes = $newHeavierBoxPacker->doVolumePacking(); |
| 232 | 232 | if (count($newHeavierBoxes) > 1) { //found an edge case in packing algorithm that *increased* box count |
| 233 | - $this->logger->log(LogLevel::INFO, "[REDISTRIBUTING WEIGHT] Abandoning redistribution, because new packing is less efficient than original"); |
|
| 233 | + $this->logger->log(LogLevel::INFO, "[REDISTRIBUTING WEIGHT] Abandoning redistribution, because new packing is less efficient than original"); |
|
| 234 | 234 | return $aPackedBoxes; |
| 235 | 235 | } |
| 236 | 236 | |
@@ -262,7 +262,7 @@ discard block |
||
| 262 | 262 | * @return PackedBox packed box |
| 263 | 263 | */ |
| 264 | 264 | public function packIntoBox(Box $aBox, ItemList $aItems) { |
| 265 | - $this->logger->log(LogLevel::DEBUG, "[EVALUATING BOX] {$aBox->getReference()}"); |
|
| 265 | + $this->logger->log(LogLevel::DEBUG, "[EVALUATING BOX] {$aBox->getReference()}"); |
|
| 266 | 266 | |
| 267 | 267 | $packedItems = new ItemList; |
| 268 | 268 | $remainingDepth = $aBox->getInnerDepth(); |
@@ -271,7 +271,7 @@ discard block |
||
| 271 | 271 | $remainingLength = $aBox->getInnerLength(); |
| 272 | 272 | |
| 273 | 273 | $layerWidth = $layerLength = $layerDepth = 0; |
| 274 | - while(!$aItems->isEmpty()) { |
|
| 274 | + while (!$aItems->isEmpty()) { |
|
| 275 | 275 | |
| 276 | 276 | $itemToPack = $aItems->top(); |
| 277 | 277 | |
@@ -280,9 +280,9 @@ discard block |
||
| 280 | 280 | continue; |
| 281 | 281 | } |
| 282 | 282 | |
| 283 | - $this->logger->log(LogLevel::DEBUG, "evaluating item {$itemToPack->getDescription()}"); |
|
| 284 | - $this->logger->log(LogLevel::DEBUG, "remaining width: {$remainingWidth}, length: {$remainingLength}, depth: {$remainingDepth}"); |
|
| 285 | - $this->logger->log(LogLevel::DEBUG, "layerWidth: {$layerWidth}, layerLength: {$layerLength}, layerDepth: {$layerDepth}"); |
|
| 283 | + $this->logger->log(LogLevel::DEBUG, "evaluating item {$itemToPack->getDescription()}"); |
|
| 284 | + $this->logger->log(LogLevel::DEBUG, "remaining width: {$remainingWidth}, length: {$remainingLength}, depth: {$remainingDepth}"); |
|
| 285 | + $this->logger->log(LogLevel::DEBUG, "layerWidth: {$layerWidth}, layerLength: {$layerLength}, layerDepth: {$layerDepth}"); |
|
| 286 | 286 | |
| 287 | 287 | $itemWidth = $itemToPack->getWidth(); |
| 288 | 288 | $itemLength = $itemToPack->getLength(); |
@@ -298,13 +298,13 @@ discard block |
||
| 298 | 298 | if ($fitsRotatedGap < 0 || |
| 299 | 299 | ($fitsSameGap >= 0 && $fitsSameGap <= $fitsRotatedGap) || |
| 300 | 300 | ($itemWidth <= $remainingWidth && !$aItems->isEmpty() && $aItems->top() == $itemToPack && $remainingLength >= 2 * $itemLength)) { |
| 301 | - $this->logger->log(LogLevel::DEBUG, "fits (better) unrotated"); |
|
| 301 | + $this->logger->log(LogLevel::DEBUG, "fits (better) unrotated"); |
|
| 302 | 302 | $remainingLength -= $itemLength; |
| 303 | 303 | $layerLength += $itemLength; |
| 304 | 304 | $layerWidth = max($itemWidth, $layerWidth); |
| 305 | 305 | } |
| 306 | 306 | else { |
| 307 | - $this->logger->log(LogLevel::DEBUG, "fits (better) rotated"); |
|
| 307 | + $this->logger->log(LogLevel::DEBUG, "fits (better) rotated"); |
|
| 308 | 308 | $remainingLength -= $itemWidth; |
| 309 | 309 | $layerLength += $itemWidth; |
| 310 | 310 | $layerWidth = max($itemLength, $layerWidth); |
@@ -313,7 +313,7 @@ discard block |
||
| 313 | 313 | |
| 314 | 314 | //allow items to be stacked in place within the same footprint up to current layerdepth |
| 315 | 315 | $maxStackDepth = $layerDepth - $itemToPack->getDepth(); |
| 316 | - while(!$aItems->isEmpty()) { |
|
| 316 | + while (!$aItems->isEmpty()) { |
|
| 317 | 317 | $potentialStackItem = $aItems->top(); |
| 318 | 318 | if ($potentialStackItem->getDepth() <= $maxStackDepth && |
| 319 | 319 | $potentialStackItem->getWeight() <= $remainingWeight && |
@@ -330,7 +330,7 @@ discard block |
||
| 330 | 330 | } |
| 331 | 331 | else { |
| 332 | 332 | if ($remainingWidth >= min($itemWidth, $itemLength) && $layerDepth > 0 && $layerWidth > 0 && $layerLength > 0) { |
| 333 | - $this->logger->log(LogLevel::DEBUG, "No more fit in lengthwise, resetting for new row"); |
|
| 333 | + $this->logger->log(LogLevel::DEBUG, "No more fit in lengthwise, resetting for new row"); |
|
| 334 | 334 | $remainingLength += $layerLength; |
| 335 | 335 | $remainingWidth -= $layerWidth; |
| 336 | 336 | $layerWidth = $layerLength = 0; |
@@ -338,7 +338,7 @@ discard block |
||
| 338 | 338 | } |
| 339 | 339 | |
| 340 | 340 | if ($remainingLength < min($itemWidth, $itemLength) || $layerDepth == 0) { |
| 341 | - $this->logger->log(LogLevel::DEBUG, "doesn't fit on layer even when empty"); |
|
| 341 | + $this->logger->log(LogLevel::DEBUG, "doesn't fit on layer even when empty"); |
|
| 342 | 342 | $aItems->extract(); |
| 343 | 343 | continue; |
| 344 | 344 | } |
@@ -348,10 +348,10 @@ discard block |
||
| 348 | 348 | $remainingDepth -= $layerDepth; |
| 349 | 349 | |
| 350 | 350 | $layerWidth = $layerLength = $layerDepth = 0; |
| 351 | - $this->logger->log(LogLevel::DEBUG, "doesn't fit, so starting next vertical layer"); |
|
| 351 | + $this->logger->log(LogLevel::DEBUG, "doesn't fit, so starting next vertical layer"); |
|
| 352 | 352 | } |
| 353 | 353 | } |
| 354 | - $this->logger->log(LogLevel::DEBUG, "done with this box"); |
|
| 354 | + $this->logger->log(LogLevel::DEBUG, "done with this box"); |
|
| 355 | 355 | return new PackedBox($aBox, $packedItems, $remainingWidth, $remainingLength, $remainingDepth, $remainingWeight); |
| 356 | 356 | } |
| 357 | 357 | |
@@ -60,14 +60,12 @@ discard block |
||
| 60 | 60 | public function setItems($aItems) { |
| 61 | 61 | if ($aItems instanceof ItemList) { |
| 62 | 62 | $this->items = clone $aItems; |
| 63 | - } |
|
| 64 | - else if (is_array($aItems)) { |
|
| 63 | + } else if (is_array($aItems)) { |
|
| 65 | 64 | $this->items = new ItemList(); |
| 66 | 65 | foreach ($aItems as $item) { |
| 67 | 66 | $this->items->insert($item); |
| 68 | 67 | } |
| 69 | - } |
|
| 70 | - else { |
|
| 68 | + } else { |
|
| 71 | 69 | throw new \RuntimeException('Not a valid list of items'); |
| 72 | 70 | } |
| 73 | 71 | } |
@@ -183,11 +181,9 @@ discard block |
||
| 183 | 181 | $boxWeight = $packedBox->getWeight(); |
| 184 | 182 | if ($boxWeight > $targetWeight) { |
| 185 | 183 | $overWeightBoxes[] = $packedBox; |
| 186 | - } |
|
| 187 | - else if ($boxWeight < $targetWeight) { |
|
| 184 | + } else if ($boxWeight < $targetWeight) { |
|
| 188 | 185 | $underWeightBoxes[] = $packedBox; |
| 189 | - } |
|
| 190 | - else { |
|
| 186 | + } else { |
|
| 191 | 187 | $packedBoxes->insert($packedBox); //target weight, so we'll keep these |
| 192 | 188 | } |
| 193 | 189 | } |
@@ -302,8 +298,7 @@ discard block |
||
| 302 | 298 | $remainingLength -= $itemLength; |
| 303 | 299 | $layerLength += $itemLength; |
| 304 | 300 | $layerWidth = max($itemWidth, $layerWidth); |
| 305 | - } |
|
| 306 | - else { |
|
| 301 | + } else { |
|
| 307 | 302 | $this->logger->log(LogLevel::DEBUG, "fits (better) rotated"); |
| 308 | 303 | $remainingLength -= $itemWidth; |
| 309 | 304 | $layerLength += $itemWidth; |
@@ -322,13 +317,11 @@ discard block |
||
| 322 | 317 | $remainingWeight -= $potentialStackItem->getWeight(); |
| 323 | 318 | $maxStackDepth -= $potentialStackItem->getDepth(); |
| 324 | 319 | $packedItems->insert($aItems->extract()); |
| 325 | - } |
|
| 326 | - else { |
|
| 320 | + } else { |
|
| 327 | 321 | break; |
| 328 | 322 | } |
| 329 | 323 | } |
| 330 | - } |
|
| 331 | - else { |
|
| 324 | + } else { |
|
| 332 | 325 | if ($remainingWidth >= min($itemWidth, $itemLength) && $layerDepth > 0 && $layerWidth > 0 && $layerLength > 0) { |
| 333 | 326 | $this->logger->log(LogLevel::DEBUG, "No more fit in lengthwise, resetting for new row"); |
| 334 | 327 | $remainingLength += $layerLength; |
@@ -1,32 +1,32 @@ discard block |
||
| 1 | 1 | <?php |
| 2 | 2 | /** |
| 3 | - * Box packing (3D bin packing, knapsack problem) |
|
| 4 | - * @package BoxPacker |
|
| 5 | - * @author Doug Wright |
|
| 6 | - */ |
|
| 7 | - namespace DVDoug\BoxPacker; |
|
| 3 | + * Box packing (3D bin packing, knapsack problem) |
|
| 4 | + * @package BoxPacker |
|
| 5 | + * @author Doug Wright |
|
| 6 | + */ |
|
| 7 | + namespace DVDoug\BoxPacker; |
|
| 8 | 8 | |
| 9 | - /** |
|
| 10 | - * List of items to be packed, ordered by volume |
|
| 11 | - * @author Doug Wright |
|
| 12 | - * @package BoxPacker |
|
| 13 | - */ |
|
| 14 | - class ItemList extends \SplMaxHeap { |
|
| 9 | + /** |
|
| 10 | + * List of items to be packed, ordered by volume |
|
| 11 | + * @author Doug Wright |
|
| 12 | + * @package BoxPacker |
|
| 13 | + */ |
|
| 14 | + class ItemList extends \SplMaxHeap { |
|
| 15 | 15 | |
| 16 | 16 | /** |
| 17 | 17 | * Compare elements in order to place them correctly in the heap while sifting up. |
| 18 | 18 | * @see \SplMaxHeap::compare() |
| 19 | 19 | */ |
| 20 | 20 | public function compare($aItemA, $aItemB) { |
| 21 | - if ($aItemA->getVolume() > $aItemB->getVolume()) { |
|
| 21 | + if ($aItemA->getVolume() > $aItemB->getVolume()) { |
|
| 22 | 22 | return 1; |
| 23 | - } |
|
| 24 | - else if ($aItemA->getVolume() < $aItemB->getVolume()) { |
|
| 23 | + } |
|
| 24 | + else if ($aItemA->getVolume() < $aItemB->getVolume()) { |
|
| 25 | 25 | return -1; |
| 26 | - } |
|
| 27 | - else { |
|
| 26 | + } |
|
| 27 | + else { |
|
| 28 | 28 | return 0; |
| 29 | - } |
|
| 29 | + } |
|
| 30 | 30 | } |
| 31 | 31 | |
| 32 | 32 | /** |
@@ -34,11 +34,11 @@ discard block |
||
| 34 | 34 | * @return array |
| 35 | 35 | */ |
| 36 | 36 | public function asArray() { |
| 37 | - $return = []; |
|
| 38 | - foreach (clone $this as $item) { |
|
| 37 | + $return = []; |
|
| 38 | + foreach (clone $this as $item) { |
|
| 39 | 39 | $return[] = $item; |
| 40 | - } |
|
| 41 | - return $return; |
|
| 40 | + } |
|
| 41 | + return $return; |
|
| 42 | 42 | } |
| 43 | 43 | |
| 44 | - } |
|
| 44 | + } |
|
@@ -20,11 +20,9 @@ |
||
| 20 | 20 | public function compare($aItemA, $aItemB) { |
| 21 | 21 | if ($aItemA->getVolume() > $aItemB->getVolume()) { |
| 22 | 22 | return 1; |
| 23 | - } |
|
| 24 | - else if ($aItemA->getVolume() < $aItemB->getVolume()) { |
|
| 23 | + } else if ($aItemA->getVolume() < $aItemB->getVolume()) { |
|
| 25 | 24 | return -1; |
| 26 | - } |
|
| 27 | - else { |
|
| 25 | + } else { |
|
| 28 | 26 | return 0; |
| 29 | 27 | } |
| 30 | 28 | } |
@@ -1,17 +1,17 @@ discard block |
||
| 1 | 1 | <?php |
| 2 | 2 | /** |
| 3 | - * Box packing (3D bin packing, knapsack problem) |
|
| 4 | - * @package BoxPacker |
|
| 5 | - * @author Doug Wright |
|
| 6 | - */ |
|
| 7 | - namespace DVDoug\BoxPacker; |
|
| 3 | + * Box packing (3D bin packing, knapsack problem) |
|
| 4 | + * @package BoxPacker |
|
| 5 | + * @author Doug Wright |
|
| 6 | + */ |
|
| 7 | + namespace DVDoug\BoxPacker; |
|
| 8 | 8 | |
| 9 | - /** |
|
| 10 | - * A "box" with items |
|
| 11 | - * @author Doug Wright |
|
| 12 | - * @package BoxPacker |
|
| 13 | - */ |
|
| 14 | - class PackedBox { |
|
| 9 | + /** |
|
| 10 | + * A "box" with items |
|
| 11 | + * @author Doug Wright |
|
| 12 | + * @package BoxPacker |
|
| 13 | + */ |
|
| 14 | + class PackedBox { |
|
| 15 | 15 | |
| 16 | 16 | /** |
| 17 | 17 | * Box used |
@@ -60,7 +60,7 @@ discard block |
||
| 60 | 60 | * @return Box |
| 61 | 61 | */ |
| 62 | 62 | public function getBox() { |
| 63 | - return $this->box; |
|
| 63 | + return $this->box; |
|
| 64 | 64 | } |
| 65 | 65 | |
| 66 | 66 | /** |
@@ -68,7 +68,7 @@ discard block |
||
| 68 | 68 | * @return ItemList |
| 69 | 69 | */ |
| 70 | 70 | public function getItems() { |
| 71 | - return $this->items; |
|
| 71 | + return $this->items; |
|
| 72 | 72 | } |
| 73 | 73 | |
| 74 | 74 | /** |
@@ -77,16 +77,16 @@ discard block |
||
| 77 | 77 | */ |
| 78 | 78 | public function getWeight() { |
| 79 | 79 | |
| 80 | - if (!is_null($this->weight)) { |
|
| 80 | + if (!is_null($this->weight)) { |
|
| 81 | 81 | return $this->weight; |
| 82 | - } |
|
| 82 | + } |
|
| 83 | 83 | |
| 84 | - $this->weight = $this->box->getEmptyWeight(); |
|
| 85 | - $items = clone $this->items; |
|
| 86 | - foreach ($items as $item) { |
|
| 84 | + $this->weight = $this->box->getEmptyWeight(); |
|
| 85 | + $items = clone $this->items; |
|
| 86 | + foreach ($items as $item) { |
|
| 87 | 87 | $this->weight += $item->getWeight(); |
| 88 | - } |
|
| 89 | - return $this->weight; |
|
| 88 | + } |
|
| 89 | + return $this->weight; |
|
| 90 | 90 | } |
| 91 | 91 | |
| 92 | 92 | /** |
@@ -94,7 +94,7 @@ discard block |
||
| 94 | 94 | * @return int |
| 95 | 95 | */ |
| 96 | 96 | public function getRemainingWidth() { |
| 97 | - return $this->remainingWidth; |
|
| 97 | + return $this->remainingWidth; |
|
| 98 | 98 | } |
| 99 | 99 | |
| 100 | 100 | /** |
@@ -102,7 +102,7 @@ discard block |
||
| 102 | 102 | * @return int |
| 103 | 103 | */ |
| 104 | 104 | public function getRemainingLength() { |
| 105 | - return $this->remainingLength; |
|
| 105 | + return $this->remainingLength; |
|
| 106 | 106 | } |
| 107 | 107 | |
| 108 | 108 | /** |
@@ -110,7 +110,7 @@ discard block |
||
| 110 | 110 | * @return int |
| 111 | 111 | */ |
| 112 | 112 | public function getRemainingDepth() { |
| 113 | - return $this->remainingDepth; |
|
| 113 | + return $this->remainingDepth; |
|
| 114 | 114 | } |
| 115 | 115 | |
| 116 | 116 | /** |
@@ -118,7 +118,7 @@ discard block |
||
| 118 | 118 | * @return int |
| 119 | 119 | */ |
| 120 | 120 | public function getRemainingWeight() { |
| 121 | - return $this->remainingWeight; |
|
| 121 | + return $this->remainingWeight; |
|
| 122 | 122 | } |
| 123 | 123 | |
| 124 | 124 | /** |
@@ -126,14 +126,14 @@ discard block |
||
| 126 | 126 | * @return float |
| 127 | 127 | */ |
| 128 | 128 | public function getVolumeUtilisation() { |
| 129 | - $itemVolume = 0; |
|
| 129 | + $itemVolume = 0; |
|
| 130 | 130 | |
| 131 | - /** @var Item $item */ |
|
| 132 | - foreach (clone $this->items as $item) { |
|
| 131 | + /** @var Item $item */ |
|
| 132 | + foreach (clone $this->items as $item) { |
|
| 133 | 133 | $itemVolume += $item->getVolume(); |
| 134 | - } |
|
| 134 | + } |
|
| 135 | 135 | |
| 136 | - return round($itemVolume / $this->box->getInnerVolume() * 100, 1); |
|
| 136 | + return round($itemVolume / $this->box->getInnerVolume() * 100, 1); |
|
| 137 | 137 | } |
| 138 | 138 | |
| 139 | 139 | |
@@ -148,12 +148,12 @@ discard block |
||
| 148 | 148 | * @param int $aRemainingWeight |
| 149 | 149 | */ |
| 150 | 150 | public function __construct(Box $aBox, ItemList $aItemList, $aRemainingWidth, $aRemainingLength, $aRemainingDepth, $aRemainingWeight) { |
| 151 | - $this->box = $aBox; |
|
| 152 | - $this->items = $aItemList; |
|
| 153 | - $this->remainingWidth = $aRemainingWidth; |
|
| 154 | - $this->remainingLength = $aRemainingLength; |
|
| 155 | - $this->remainingDepth = $aRemainingDepth; |
|
| 156 | - $this->remainingWeight = $aRemainingWeight; |
|
| 151 | + $this->box = $aBox; |
|
| 152 | + $this->items = $aItemList; |
|
| 153 | + $this->remainingWidth = $aRemainingWidth; |
|
| 154 | + $this->remainingLength = $aRemainingLength; |
|
| 155 | + $this->remainingDepth = $aRemainingDepth; |
|
| 156 | + $this->remainingWeight = $aRemainingWeight; |
|
| 157 | 157 | } |
| 158 | 158 | |
| 159 | - } |
|
| 159 | + } |
|