/**
* 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;
}