Author: Doug Wright
Inheritance: extends SplMaxHeap
Example #1
0
 /**
  * Pack as many items as possible into specific given box
  * @param Box      $aBox
  * @param ItemList $aItems
  * @return PackedBox packed box
  */
 public function packIntoBox(Box $aBox, ItemList $aItems)
 {
     $this->logger->log(LogLevel::DEBUG, "[EVALUATING BOX] {$aBox->getReference()}");
     $packedItems = new ItemList();
     $remainingDepth = $aBox->getInnerDepth();
     $remainingWeight = $aBox->getMaxWeight() - $aBox->getEmptyWeight();
     $remainingWidth = $aBox->getInnerWidth();
     $remainingLength = $aBox->getInnerLength();
     $layerWidth = $layerLength = $layerDepth = 0;
     while (!$aItems->isEmpty()) {
         $itemToPack = $aItems->top();
         if ($itemToPack->getDepth() > $remainingDepth || $itemToPack->getWeight() > $remainingWeight) {
             break;
         }
         $this->logger->log(LogLevel::DEBUG, "evaluating item {$itemToPack->getDescription()}");
         $this->logger->log(LogLevel::DEBUG, "remaining width: {$remainingWidth}, length: {$remainingLength}, depth: {$remainingDepth}");
         $this->logger->log(LogLevel::DEBUG, "layerWidth: {$layerWidth}, layerLength: {$layerLength}, layerDepth: {$layerDepth}");
         $itemWidth = $itemToPack->getWidth();
         $itemLength = $itemToPack->getLength();
         $fitsSameGap = min($remainingWidth - $itemWidth, $remainingLength - $itemLength);
         $fitsRotatedGap = min($remainingWidth - $itemLength, $remainingLength - $itemWidth);
         if ($fitsSameGap >= 0 || $fitsRotatedGap >= 0) {
             $packedItems->insert($aItems->extract());
             $remainingWeight -= $itemToPack->getWeight();
             if ($fitsRotatedGap < 0 || $fitsSameGap >= 0 && $fitsSameGap <= $fitsRotatedGap || !$aItems->isEmpty() && $aItems->top() == $itemToPack && $remainingLength >= 2 * $itemLength) {
                 $this->logger->log(LogLevel::DEBUG, "fits (better) unrotated");
                 $remainingLength -= $itemLength;
                 $layerLength += $itemLength;
                 $layerWidth = max($itemWidth, $layerWidth);
             } else {
                 $this->logger->log(LogLevel::DEBUG, "fits (better) rotated");
                 $remainingLength -= $itemWidth;
                 $layerLength += $itemWidth;
                 $layerWidth = max($itemLength, $layerWidth);
             }
             $layerDepth = max($layerDepth, $itemToPack->getDepth());
             //greater than 0, items will always be less deep
             //allow items to be stacked in place within the same footprint up to current layerdepth
             $maxStackDepth = $layerDepth - $itemToPack->getDepth();
             while (!$aItems->isEmpty()) {
                 $potentialStackItem = $aItems->top();
                 if ($potentialStackItem->getDepth() <= $maxStackDepth && $potentialStackItem->getWeight() <= $remainingWeight && $potentialStackItem->getWidth() <= $itemToPack->getWidth() && $potentialStackItem->getLength() <= $itemToPack->getLength()) {
                     $remainingWeight -= $potentialStackItem->getWeight();
                     $maxStackDepth -= $potentialStackItem->getDepth();
                     $packedItems->insert($aItems->extract());
                 } else {
                     break;
                 }
             }
         } else {
             if ($remainingWidth >= min($itemWidth, $itemLength) && $layerDepth > 0 && $layerWidth > 0 && $layerLength > 0) {
                 $this->logger->log(LogLevel::DEBUG, "No more fit in lengthwise, resetting for new row");
                 $remainingLength += $layerLength;
                 $remainingWidth -= $layerWidth;
                 $layerWidth = $layerLength = 0;
                 continue;
             }
             if ($remainingLength < min($itemWidth, $itemLength) || $layerDepth == 0) {
                 $this->logger->log(LogLevel::DEBUG, "doesn't fit on layer even when empty");
                 break;
             }
             $remainingWidth = $layerWidth ? min(floor($layerWidth * 1.1), $aBox->getInnerWidth()) : $aBox->getInnerWidth();
             $remainingLength = $layerLength ? min(floor($layerLength * 1.1), $aBox->getInnerLength()) : $aBox->getInnerLength();
             $remainingDepth -= $layerDepth;
             $layerWidth = $layerLength = $layerDepth = 0;
             $this->logger->log(LogLevel::DEBUG, "doesn't fit, so starting next vertical layer");
         }
     }
     $this->logger->log(LogLevel::DEBUG, "done with this box");
     return new PackedBox($aBox, $packedItems, $remainingWidth, $remainingLength, $remainingDepth, $remainingWeight);
 }
Example #2
0
 /**
  * Figure out if we can stack the next item vertically on top of this rather than side by side
  * Used when we've packed a tall item, and have just put a shorter one next to it
  * @param ItemList $packedItems
  * @param int $maxWidth
  * @param int $maxLength
  * @param int $maxDepth
  */
 protected function tryAndStackItemsIntoSpace(ItemList $packedItems, $maxWidth, $maxLength, $maxDepth)
 {
     while (!$this->items->isEmpty() && $this->remainingWeight >= $this->items->top()->getWeight()) {
         $stackedItem = $this->findBestOrientation($this->items->top(), null, null, $maxWidth, $maxLength, $maxDepth);
         if ($stackedItem) {
             $this->remainingWeight -= $this->items->top()->getWeight();
             $maxDepth -= $stackedItem->getDepth();
             $packedItems->insert($this->items->extract());
         } else {
             break;
         }
     }
 }
Example #3
0
 /**
  * Pack as many items as possible into specific given box
  * @param Box      $box
  * @param ItemList $items
  * @return PackedBox packed box
  */
 public function packIntoBox(Box $box, ItemList $items)
 {
     $this->logger->log(LogLevel::DEBUG, "[EVALUATING BOX] {$box->getReference()}");
     $packedItems = new ItemList();
     $remainingDepth = $box->getInnerDepth();
     $remainingWeight = $box->getMaxWeight() - $box->getEmptyWeight();
     $remainingWidth = $box->getInnerWidth();
     $remainingLength = $box->getInnerLength();
     $layerWidth = $layerLength = $layerDepth = 0;
     while (!$items->isEmpty()) {
         $itemToPack = $items->top();
         //skip items that are simply too large
         if ($this->isItemTooLargeForBox($itemToPack, $remainingDepth, $remainingWeight)) {
             $items->extract();
             continue;
         }
         $this->logger->log(LogLevel::DEBUG, "evaluating item {$itemToPack->getDescription()}");
         $this->logger->log(LogLevel::DEBUG, "remaining width: {$remainingWidth}, length: {$remainingLength}, depth: {$remainingDepth}");
         $this->logger->log(LogLevel::DEBUG, "layerWidth: {$layerWidth}, layerLength: {$layerLength}, layerDepth: {$layerDepth}");
         $itemWidth = $itemToPack->getWidth();
         $itemLength = $itemToPack->getLength();
         if ($this->fitsGap($itemToPack, $remainingWidth, $remainingLength)) {
             $packedItems->insert($items->extract());
             $remainingWeight -= $itemToPack->getWeight();
             $nextItem = !$items->isEmpty() ? $items->top() : null;
             if ($this->fitsBetterRotated($itemToPack, $nextItem, $remainingWidth, $remainingLength)) {
                 $this->logger->log(LogLevel::DEBUG, "fits (better) unrotated");
                 $remainingLength -= $itemLength;
                 $layerLength += $itemLength;
                 $layerWidth = max($itemWidth, $layerWidth);
             } else {
                 $this->logger->log(LogLevel::DEBUG, "fits (better) rotated");
                 $remainingLength -= $itemWidth;
                 $layerLength += $itemWidth;
                 $layerWidth = max($itemLength, $layerWidth);
             }
             $layerDepth = max($layerDepth, $itemToPack->getDepth());
             //greater than 0, items will always be less deep
             //allow items to be stacked in place within the same footprint up to current layerdepth
             $maxStackDepth = $layerDepth - $itemToPack->getDepth();
             while (!$items->isEmpty() && $this->canStackItemInLayer($itemToPack, $items->top(), $maxStackDepth, $remainingWeight)) {
                 $remainingWeight -= $items->top()->getWeight();
                 $maxStackDepth -= $items->top()->getDepth();
                 $packedItems->insert($items->extract());
             }
         } else {
             if ($remainingWidth >= min($itemWidth, $itemLength) && $this->isLayerStarted($layerWidth, $layerLength, $layerDepth)) {
                 $this->logger->log(LogLevel::DEBUG, "No more fit in lengthwise, resetting for new row");
                 $remainingLength += $layerLength;
                 $remainingWidth -= $layerWidth;
                 $layerWidth = $layerLength = 0;
                 continue;
             } elseif ($remainingLength < min($itemWidth, $itemLength) || $layerDepth == 0) {
                 $this->logger->log(LogLevel::DEBUG, "doesn't fit on layer even when empty");
                 $items->extract();
                 continue;
             }
             $remainingWidth = $layerWidth ? min(floor($layerWidth * 1.1), $box->getInnerWidth()) : $box->getInnerWidth();
             $remainingLength = $layerLength ? min(floor($layerLength * 1.1), $box->getInnerLength()) : $box->getInnerLength();
             $remainingDepth -= $layerDepth;
             $layerWidth = $layerLength = $layerDepth = 0;
             $this->logger->log(LogLevel::DEBUG, "doesn't fit, so starting next vertical layer");
         }
     }
     $this->logger->log(LogLevel::DEBUG, "done with this box");
     return new PackedBox($box, $packedItems, $remainingWidth, $remainingLength, $remainingDepth, $remainingWeight);
 }
Example #4
0
 /**
  * Pack items into boxes using the principle of largest volume item first
  *
  * @throws \RuntimeException
  * @return PackedBoxList
  */
 public function doVolumePacking()
 {
     $packedBoxes = new PackedBoxList();
     //Keep going until everything packed
     while ($this->items->count()) {
         $boxesToEvaluate = clone $this->boxes;
         $packedBoxesIteration = new PackedBoxList();
         //Loop through boxes starting with smallest, see what happens
         while (!$boxesToEvaluate->isEmpty()) {
             $box = $boxesToEvaluate->extract();
             $volumePacker = new VolumePacker($box, clone $this->items);
             $volumePacker->setLogger($this->logger);
             $packedBox = $volumePacker->pack();
             if ($packedBox->getItems()->count()) {
                 $packedBoxesIteration->insert($packedBox);
                 //Have we found a single box that contains everything?
                 if ($packedBox->getItems()->count() === $this->items->count()) {
                     break;
                 }
             }
         }
         //Check iteration was productive
         if ($packedBoxesIteration->isEmpty()) {
             throw new \RuntimeException('Item ' . $this->items->top()->getDescription() . ' is too large to fit into any box');
         }
         //Find best box of iteration, and remove packed items from unpacked list
         $bestBox = $packedBoxesIteration->top();
         $unPackedItems = $this->items->asArray();
         foreach (clone $bestBox->getItems() as $packedItem) {
             foreach ($unPackedItems as $unpackedKey => $unpackedItem) {
                 if ($packedItem === $unpackedItem) {
                     unset($unPackedItems[$unpackedKey]);
                     break;
                 }
             }
         }
         $unpackedItemList = new ItemList();
         foreach ($unPackedItems as $unpackedItem) {
             $unpackedItemList->insert($unpackedItem);
         }
         $this->items = $unpackedItemList;
         $packedBoxes->insert($bestBox);
     }
     return $packedBoxes;
 }