Passed
Push — master ( 3f7235...602f18 )
by Doug
07:56
created

PackedBoxList::insert()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 2
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 4
ccs 3
cts 3
cp 1
crap 1
rs 10
1
<?php
2
/**
3
 * Box packing (3D bin packing, knapsack problem).
4
 *
5
 * @author Doug Wright
6
 */
7
declare(strict_types=1);
8
9
namespace DVDoug\BoxPacker;
10
11
use ArrayIterator;
12
use function count;
13
use Countable;
14
use IteratorAggregate;
15
use function json_encode;
16
use JsonSerializable;
17
use function reset;
18
use ReturnTypeWillChange;
19
use function round;
20
use Traversable;
21
use function urlencode;
22
use function usort;
23
24
/**
25
 * List of packed boxes.
26
 *
27
 * @author Doug Wright
28
 */
29
class PackedBoxList implements IteratorAggregate, Countable, JsonSerializable
30
{
31
    /**
32
     * List containing boxes.
33
     *
34
     * @var PackedBox[]
35
     */
36
    private $list = [];
37
38
    /**
39
     * Has this list already been sorted?
40
     *
41
     * @var bool
42
     */
43
    private $isSorted = false;
44
45
    /**
46
     * @return Traversable<PackedBox>
47
     */
48 24
    public function getIterator(): Traversable
49
    {
50 24
        if (!$this->isSorted) {
51 24
            usort($this->list, [$this, 'compare']);
52 24
            $this->isSorted = true;
53
        }
54
55 24
        return new ArrayIterator($this->list);
56
    }
57
58
    /**
59
     * Number of items in list.
60
     */
61 36
    public function count(): int
62
    {
63 36
        return count($this->list);
64
    }
65
66 42
    public function insert(PackedBox $item): void
67
    {
68 42
        $this->list[] = $item;
69 42
        $this->isSorted = false;
70 42
    }
71
72
    /**
73
     * Do a bulk insert.
74
     *
75
     * @internal
76
     *
77
     * @param PackedBox[] $boxes
78
     */
79 11
    public function insertFromArray(array $boxes): void
80
    {
81 11
        foreach ($boxes as $box) {
82 11
            $this->insert($box);
83
        }
84 11
    }
85
86
    /**
87
     * @internal
88
     */
89 7
    public function top(): PackedBox
90
    {
91 7
        if (!$this->isSorted) {
92 7
            usort($this->list, [$this, 'compare']);
93 7
            $this->isSorted = true;
94
        }
95
96 7
        return reset($this->list);
97
    }
98
99 11
    private function compare(PackedBox $boxA, PackedBox $boxB): int
100
    {
101 11
        $choice = $boxB->getItems()->count() <=> $boxA->getItems()->count();
102 11
        if ($choice === 0) {
103 8
            $choice = $boxB->getInnerVolume() <=> $boxA->getInnerVolume();
104
        }
105 11
        if ($choice === 0) {
106 8
            $choice = $boxA->getWeight() <=> $boxB->getWeight();
107
        }
108
109 11
        return $choice;
110
    }
111
112
    /**
113
     * Calculate the average (mean) weight of the boxes.
114
     */
115 12
    public function getMeanWeight(): float
116
    {
117 12
        $meanWeight = 0;
118
119
        /** @var PackedBox $box */
120 12
        foreach ($this->list as $box) {
121 12
            $meanWeight += $box->getWeight();
122
        }
123
124 12
        return $meanWeight / count($this->list);
125
    }
126
127
    /**
128
     * Calculate the average (mean) weight of the boxes.
129
     */
130 10
    public function getMeanItemWeight(): float
131
    {
132 10
        $meanWeight = 0;
133
134
        /** @var PackedBox $box */
135 10
        foreach ($this->list as $box) {
136 10
            $meanWeight += $box->getItemWeight();
137
        }
138
139 10
        return $meanWeight / count($this->list);
140
    }
141
142
    /**
143
     * Calculate the variance in weight between these boxes.
144
     */
145 11
    public function getWeightVariance(): float
146
    {
147 11
        $mean = $this->getMeanWeight();
148
149 11
        $weightVariance = 0;
150
        /** @var PackedBox $box */
151 11
        foreach ($this->list as $box) {
152 11
            $weightVariance += ($box->getWeight() - $mean) ** 2;
153
        }
154
155 11
        return round($weightVariance / count($this->list), 1);
156
    }
157
158
    /**
159
     * Get volume utilisation of the set of packed boxes.
160
     */
161 1
    public function getVolumeUtilisation(): float
162
    {
163 1
        $itemVolume = 0;
164 1
        $boxVolume = 0;
165
166
        /** @var PackedBox $box */
167 1
        foreach ($this as $box) {
168 1
            $boxVolume += $box->getInnerVolume();
169
170
            /** @var PackedItem $item */
171 1
            foreach ($box->getItems() as $item) {
172 1
                $itemVolume += ($item->getItem()->getWidth() * $item->getItem()->getLength() * $item->getItem()->getDepth());
173
            }
174
        }
175
176 1
        return round($itemVolume / $boxVolume * 100, 1);
177
    }
178
179
    /**
180
     * Create a custom website visualiser URL for this packing.
181
     */
182 1
    public function generateVisualisationURL(): string
183
    {
184 1
        return 'https://boxpacker.io/en/latest/visualiser.html?packing=' . urlencode(json_encode($this));
185
    }
186
187 2
    #[ReturnTypeWillChange]
188
    public function jsonSerialize()/*: mixed*/
189
    {
190 2
        return $this->list;
191
    }
192
}
193