forked from dvdoug/BoxPacker
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathWeightRedistributor.php
More file actions
139 lines (117 loc) · 5.82 KB
/
WeightRedistributor.php
File metadata and controls
139 lines (117 loc) · 5.82 KB
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
128
129
130
131
132
133
134
135
136
137
138
139
<?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
*
* @param BoxList $boxList
*/
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
/** @var Item $overWeightBoxItem */
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 = $underWeightBox->getItems()->asArray();
$newItemsForLighterBox[] = $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() === count($newItemsForLighterBox)) { //new item fits
$this->logger->log(LogLevel::DEBUG, 'New item fits');
unset($overWeightBoxItems[$oi]); //now packed in different box
if (count($overWeightBoxItems) > 0) {
$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 ($newHeavierBoxes->count()
> 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();
} else {
unset($overWeightBoxes[$o]);
}
$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;
}
}