/** * Given a solution set of packed boxes, repack them to achieve optimum weight distribution * * @param ArtatusPackedBoxList $aPackedBoxes * @return ArtatusPackedBoxList */ public function redistributeWeight(ArtatusPackedBoxList $aPackedBoxes) { $targetWeight = $aPackedBoxes->getMeanWeight(); if ($this->debug) { echo "repacking for weight distribution, weight variance {$aPackedBoxes->getWeightVariance()}, target weight {$targetWeight}"; } $packedBoxes = new ArtatusPackedBoxList(); $overWeightBoxes = array(); $underWeightBoxes = array(); 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; if ($this->debug) { echo '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, array($packedBoxes, 'reverseCompare')); usort($underWeightBoxes, array($packedBoxes, 'reverseCompare')); break 3; } } } } } while ($tryRepack); //Combine back into a single list $packedBoxes->insertFromArray($overWeightBoxes); $packedBoxes->insertFromArray($underWeightBoxes); return $packedBoxes; }