forked from phux/BoxPacker
-
Notifications
You must be signed in to change notification settings - Fork 0
/
WeightRedistributor.php
127 lines (106 loc) · 5.35 KB
/
WeightRedistributor.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
<?php
/**
* Box packing (3D bin packing, knapsack problem)
* @package BoxPacker
* @author Doug Wright
*/
namespace DVDoug\BoxPacker;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait;
use Psr\Log\LogLevel;
use Psr\Log\NullLogger;
/**
* Actual packer
* @author Doug Wright
* @package BoxPacker
*/
class WeightRedistributor implements LoggerAwareInterface
{
use LoggerAwareTrait;
/**
* List of box sizes available to pack items into
* @var BoxList
*/
protected $boxes;
/**
* Constructor
*/
public function __construct(BoxList $boxList)
{
$this->boxes = clone $boxList;
$this->logger = new NullLogger();
}
/**
* 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;
}
}