Completed
Push — master ( 15d6df...440a91 )
by Doug
15:50
created

src/WeightRedistributor.php (1 issue)

Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * Box packing (3D bin packing, knapsack problem)
4
 * @package BoxPacker
5
 * @author Doug Wright
6
 */
7
declare(strict_types=1);
8
namespace DVDoug\BoxPacker;
9
10
use Psr\Log\LoggerAwareInterface;
11
use Psr\Log\LoggerAwareTrait;
12
use Psr\Log\LogLevel;
13
use Psr\Log\NullLogger;
14
15
/**
16
 * Actual packer
17
 * @author Doug Wright
18
 * @package BoxPacker
19
 */
20
class WeightRedistributor implements LoggerAwareInterface
21
{
22
23
    use LoggerAwareTrait;
24
25
    /**
26
     * List of box sizes available to pack items into
27
     * @var BoxList
28
     */
29
    protected $boxes;
30
31
    /**
32
     * Constructor
33
     *
34
     * @param BoxList $boxList
35
     */
36 5
    public function __construct(BoxList $boxList)
37
    {
38 5
        $this->boxes = clone $boxList;
39 5
        $this->logger = new NullLogger();
40
    }
41
42
    /**
43
     * Given a solution set of packed boxes, repack them to achieve optimum weight distribution
44
     *
45
     * @param PackedBoxList $originalBoxes
46
     * @return PackedBoxList
47
     */
48 5
    public function redistributeWeight(PackedBoxList $originalBoxes): PackedBoxList
49
    {
50
51 5
        $targetWeight = $originalBoxes->getMeanWeight();
52 5
        $this->logger->log(LogLevel::DEBUG, "repacking for weight distribution, weight variance {$originalBoxes->getWeightVariance()}, target weight {$targetWeight}");
53
54 5
        $packedBoxes = new PackedBoxList;
55
56 5
        $overWeightBoxes = [];
57 5
        $underWeightBoxes = [];
58 5
        foreach (clone $originalBoxes as $packedBox) {
59 5
            $boxWeight = $packedBox->getWeight();
60 5
            if ($boxWeight > $targetWeight) {
61 3
                $overWeightBoxes[] = $packedBox;
62 5
            } elseif ($boxWeight < $targetWeight) {
63 3
                $underWeightBoxes[] = $packedBox;
64
            } else {
65 2
                $packedBoxes->insert($packedBox); //target weight, so we'll keep these
66
            }
67
        }
68
69
        do { //Keep moving items from most overweight box to most underweight box
70 5
            $tryRepack = false;
71 5
            $this->logger->log(LogLevel::DEBUG, 'boxes under/over target: ' . count($underWeightBoxes) . '/' . count($overWeightBoxes));
72
73 5
            foreach ($underWeightBoxes as $u => $underWeightBox) {
74 3
                $this->logger->log(LogLevel::DEBUG, 'Underweight Box ' . $u);
75 3
                foreach ($overWeightBoxes as $o => $overWeightBox) {
76 3
                    $this->logger->log(LogLevel::DEBUG, 'Overweight Box ' . $o);
77 3
                    $overWeightBoxItems = $overWeightBox->getItems()->asItemArray();
78
79
                    //For each item in the heavier box, try and move it to the lighter one
80
                    /** @var Item $overWeightBoxItem */
81 3
                    foreach ($overWeightBoxItems as $oi => $overWeightBoxItem) {
82 3
                        $this->logger->log(LogLevel::DEBUG, 'Overweight Item ' . $oi);
83 3
                        if ($underWeightBox->getWeight() + $overWeightBoxItem->getWeight() > $targetWeight) {
84 3
                            $this->logger->log(LogLevel::DEBUG, 'Skipping item for hindering weight distribution');
85 3
                            continue; //skip if moving this item would hinder rather than help weight distribution
86
                        }
87
88 1
                        $newItemsForLighterBox = $underWeightBox->getItems()->asItemArray();
89 1
                        $newItemsForLighterBox[] = $overWeightBoxItem;
90
91 1
                        $newLighterBoxPacker = new Packer(); //we may need a bigger box
92 1
                        $newLighterBoxPacker->setBoxes($this->boxes);
93 1
                        $newLighterBoxPacker->setItems($newItemsForLighterBox);
0 ignored issues
show
$newItemsForLighterBox is of type array<integer,?>, but the function expects a object<DVDoug\BoxPacker\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
94 1
                        $this->logger->log(LogLevel::INFO, "[ATTEMPTING TO PACK LIGHTER BOX]");
95 1
                        $newLighterBox = $newLighterBoxPacker->doVolumePacking()->extract();
96
97 1
                        if ($newLighterBox->getItems()->count() === count($newItemsForLighterBox)) { //new item fits
98 1
                            $this->logger->log(LogLevel::DEBUG, 'New item fits');
99 1
                            unset($overWeightBoxItems[$oi]); //now packed in different box
100
101 1
                            if (count($overWeightBoxItems) > 0) {
102 1
                                $newHeavierBoxPacker = new Packer(); //we may be able to use a smaller box
103 1
                                $newHeavierBoxPacker->setBoxes($this->boxes);
104 1
                                $newHeavierBoxPacker->setItems($overWeightBoxItems);
105
106 1
                                $this->logger->log(LogLevel::INFO, "[ATTEMPTING TO PACK HEAVIER BOX]");
107 1
                                $newHeavierBoxes = $newHeavierBoxPacker->doVolumePacking();
108 1
                                if ($newHeavierBoxes->count()
109 1
                                    > 1) { //found an edge case in packing algorithm that *increased* box count
110
                                    $this->logger->log(
111
                                        LogLevel::INFO,
112
                                        "[REDISTRIBUTING WEIGHT] Abandoning redistribution, because new packing is less efficient than original"
113
                                    );
114
115
                                    return $originalBoxes;
116
                                }
117
118 1
                                $overWeightBoxes[$o] = $newHeavierBoxes->extract();
119
                            } else {
120
                                unset($overWeightBoxes[$o]);
121
                            }
122 1
                            $underWeightBoxes[$u] = $newLighterBox;
123
124 1
                            $tryRepack = true; //we did some work, so see if we can do even better
125 1
                            usort($overWeightBoxes, [$packedBoxes, 'reverseCompare']);
126 1
                            usort($underWeightBoxes, [$packedBoxes, 'reverseCompare']);
127 3
                            break 3;
128
                        }
129
                    }
130
                }
131
            }
132 5
        } while ($tryRepack);
133
134
        //Combine back into a single list
135 5
        $packedBoxes->insertFromArray($overWeightBoxes);
136 5
        $packedBoxes->insertFromArray($underWeightBoxes);
137
138 5
        return $packedBoxes;
139
    }
140
}
141