Ejemplo n.º 1
0
 /**
  * Given a solution set of packed boxes, repack them to achieve optimum weight distribution
  *
  * @param PackedBoxList $originalBoxes
  * @return PackedBoxList
  */
 public function redistributeWeight(PackedBoxList $originalBoxes)
 {
     $targetWeight = $originalBoxes->getMeanWeight();
     $this->logger->log(LogLevel::DEBUG, "repacking for weight distribution, weight variance {$originalBoxes->getWeightVariance()}, target weight {$targetWeight}");
     $packedBoxes = new PackedBoxList();
     $overWeightBoxes = [];
     $underWeightBoxes = [];
     foreach (clone $originalBoxes as $packedBox) {
         $boxWeight = $packedBox->getWeight();
         if ($boxWeight > $targetWeight) {
             $overWeightBoxes[] = $packedBox;
         } elseif ($boxWeight < $targetWeight) {
             $underWeightBoxes[] = $packedBox;
         } else {
             $packedBoxes->insert($packedBox);
             //target weight, so we'll keep these
         }
     }
     do {
         //Keep moving items from most overweight box to most underweight box
         $tryRepack = false;
         $this->logger->log(LogLevel::DEBUG, 'boxes under/over target: ' . count($underWeightBoxes) . '/' . count($overWeightBoxes));
         foreach ($underWeightBoxes as $u => $underWeightBox) {
             $this->logger->log(LogLevel::DEBUG, 'Underweight Box ' . $u);
             foreach ($overWeightBoxes as $o => $overWeightBox) {
                 $this->logger->log(LogLevel::DEBUG, 'Overweight Box ' . $o);
                 $overWeightBoxItems = $overWeightBox->getItems()->asArray();
                 //For each item in the heavier box, try and move it to the lighter one
                 foreach ($overWeightBoxItems as $oi => $overWeightBoxItem) {
                     $this->logger->log(LogLevel::DEBUG, 'Overweight Item ' . $oi);
                     if ($underWeightBox->getWeight() + $overWeightBoxItem->getWeight() > $targetWeight) {
                         $this->logger->log(LogLevel::DEBUG, 'Skipping item for hindering weight distribution');
                         continue;
                         //skip if moving this item would hinder rather than help weight distribution
                     }
                     $newItemsForLighterBox = clone $underWeightBox->getItems();
                     $newItemsForLighterBox->insert($overWeightBoxItem);
                     $newLighterBoxPacker = new Packer();
                     //we may need a bigger box
                     $newLighterBoxPacker->setBoxes($this->boxes);
                     $newLighterBoxPacker->setItems($newItemsForLighterBox);
                     $this->logger->log(LogLevel::INFO, "[ATTEMPTING TO PACK LIGHTER BOX]");
                     $newLighterBox = $newLighterBoxPacker->doVolumePacking()->extract();
                     if ($newLighterBox->getItems()->count() === $newItemsForLighterBox->count()) {
                         //new item fits
                         $this->logger->log(LogLevel::DEBUG, 'New item fits');
                         unset($overWeightBoxItems[$oi]);
                         //now packed in different box
                         $newHeavierBoxPacker = new Packer();
                         //we may be able to use a smaller box
                         $newHeavierBoxPacker->setBoxes($this->boxes);
                         $newHeavierBoxPacker->setItems($overWeightBoxItems);
                         $this->logger->log(LogLevel::INFO, "[ATTEMPTING TO PACK HEAVIER BOX]");
                         $newHeavierBoxes = $newHeavierBoxPacker->doVolumePacking();
                         if (count($newHeavierBoxes) > 1) {
                             //found an edge case in packing algorithm that *increased* box count
                             $this->logger->log(LogLevel::INFO, "[REDISTRIBUTING WEIGHT] Abandoning redistribution, because new packing is less efficient than original");
                             return $originalBoxes;
                         }
                         $overWeightBoxes[$o] = $newHeavierBoxes->extract();
                         $underWeightBoxes[$u] = $newLighterBox;
                         $tryRepack = true;
                         //we did some work, so see if we can do even better
                         usort($overWeightBoxes, [$packedBoxes, 'reverseCompare']);
                         usort($underWeightBoxes, [$packedBoxes, 'reverseCompare']);
                         break 3;
                     }
                 }
             }
         }
     } while ($tryRepack);
     //Combine back into a single list
     $packedBoxes->insertFromArray($overWeightBoxes);
     $packedBoxes->insertFromArray($underWeightBoxes);
     return $packedBoxes;
 }
Ejemplo n.º 2
0
 /**
  * Given a solution set of packed boxes, repack them to achieve optimum weight distribution
  *
  * @param PackedBoxList $aPackedBoxes
  * @return PackedBoxList
  */
 public function redistributeWeight(PackedBoxList $aPackedBoxes)
 {
     $targetWeight = $aPackedBoxes->getMeanWeight();
     $this->logger->log(LogLevel::DEBUG, "repacking for weight distribution, weight variance {$aPackedBoxes->getWeightVariance()}, target weight {$targetWeight}");
     $packedBoxes = new PackedBoxList();
     $overWeightBoxes = [];
     $underWeightBoxes = [];
     foreach ($aPackedBoxes as $packedBox) {
         $boxWeight = $packedBox->getWeight();
         if ($boxWeight > $targetWeight) {
             $overWeightBoxes[] = $packedBox;
         } else {
             if ($boxWeight < $targetWeight) {
                 $underWeightBoxes[] = $packedBox;
             } else {
                 $packedBoxes->insert($packedBox);
                 //target weight, so we'll keep these
             }
         }
     }
     do {
         //Keep moving items from most overweight box to most underweight box
         $tryRepack = false;
         $this->logger->log(LogLevel::DEBUG, 'boxes under/over target: ' . count($underWeightBoxes) . '/' . count($overWeightBoxes));
         foreach ($underWeightBoxes as $u => $underWeightBox) {
             foreach ($overWeightBoxes as $o => $overWeightBox) {
                 $overWeightBoxItems = $overWeightBox->getItems()->asArray();
                 //For each item in the heavier box, try and move it to the lighter one
                 foreach ($overWeightBoxItems as $oi => $overWeightBoxItem) {
                     if ($underWeightBox->getWeight() + $overWeightBoxItem->getWeight() > $targetWeight) {
                         continue;
                         //skip if moving this item would hinder rather than help weight distribution
                     }
                     $newItemsForLighterBox = clone $underWeightBox->getItems();
                     $newItemsForLighterBox->insert($overWeightBoxItem);
                     $newLighterBoxPacker = new Packer();
                     //we may need a bigger box
                     $newLighterBoxPacker->setBoxes($this->boxes);
                     $newLighterBoxPacker->setItems($newItemsForLighterBox);
                     $newLighterBox = $newLighterBoxPacker->doVolumePacking()->extract();
                     if ($newLighterBox->getItems()->count() === $newItemsForLighterBox->count()) {
                         //new item fits
                         unset($overWeightBoxItems[$oi]);
                         //now packed in different box
                         $newHeavierBoxPacker = new Packer();
                         //we may be able to use a smaller box
                         $newHeavierBoxPacker->setBoxes($this->boxes);
                         $newHeavierBoxPacker->setItems($overWeightBoxItems);
                         $overWeightBoxes[$o] = $newHeavierBoxPacker->doVolumePacking()->extract();
                         $underWeightBoxes[$u] = $newLighterBox;
                         $tryRepack = true;
                         //we did some work, so see if we can do even better
                         usort($overWeightBoxes, [$packedBoxes, 'reverseCompare']);
                         usort($underWeightBoxes, [$packedBoxes, 'reverseCompare']);
                         break 3;
                     }
                 }
             }
         }
     } while ($tryRepack);
     //Combine back into a single list
     $packedBoxes->insertFromArray($overWeightBoxes);
     $packedBoxes->insertFromArray($underWeightBoxes);
     return $packedBoxes;
 }
Ejemplo n.º 3
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;
 }