Passed
Pull Request — master (#308)
by
unknown
05:50 queued 03:55
created

ItemList::hasConstrainedItems()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 4.25

Importance

Changes 0
Metric Value
cc 4
eloc 7
c 0
b 0
f 0
nc 4
nop 0
dl 0
loc 13
ccs 6
cts 8
cp 0.75
crap 4.25
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 function array_key_last;
12
use function array_pop;
13
use function array_reverse;
14
use function array_slice;
15
16
use ArrayIterator;
17
18
use function count;
19
20
use Countable;
21
22
use function current;
23
use function end;
24
25
use IteratorAggregate;
26
27
use function key;
28
use function prev;
29
30
use Traversable;
31
32
use function usort;
33
34
/**
35
 * List of items to be packed, ordered by volume.
36
 */
37
class ItemList implements Countable, IteratorAggregate
38
{
39
    /** @var Item[] */
40
    private array $list = [];
41
42
    private bool $isSorted = false;
43
44
    private ItemSorter $sorter;
45
46
    private ?bool $hasConstrainedItems = null;
47
48
    private ?bool $hasNoRotationItems = null;
49
50 80
    public function __construct(?ItemSorter $sorter = null)
51
    {
52 80
        $this->sorter = $sorter ?: new DefaultItemSorter();
53
    }
54
55
    /**
56
     * Do a bulk create.
57
     *
58
     * @param  Item[]   $items
59
     * @return ItemList
60
     */
61 76
    public static function fromArray(array $items, bool $preSorted = false): self
62
    {
63 76
        $list = new self();
64 76
        $list->list = array_reverse($items); // internal sort is largest at the end
65 76
        $list->isSorted = $preSorted;
66
67 76
        return $list;
68
    }
69
70 84
    public function insert(Item $item, int $qty = 1): void
71
    {
72 84
        for ($i = 0; $i < $qty; ++$i) {
73 84
            $this->list[] = $item;
74
        }
75 84
        $this->isSorted = false;
76
77 84
        if (isset($this->hasConstrainedItems)) { // normally lazy evaluated, override if that's already been done
78
            $this->hasConstrainedItems = $this->hasConstrainedItems || $item instanceof ConstrainedPlacementItem;
79
        }
80
81 84
        if (isset($this->hasNoRotationItems)) { // normally lazy evaluated, override if that's already been done
82
            $this->hasNoRotationItems = $this->hasNoRotationItems || $item->getAllowedRotation() === Rotation::Never;
83
        }
84
    }
85
86
    /**
87
     * Remove item from list.
88
     */
89
    public function remove(Item $item): void
90
    {
91
        if (!$this->isSorted) {
92
            usort($this->list, [$this->sorter, 'compare']);
93
            $this->list = array_reverse($this->list); // internal sort is largest at the end
94
            $this->isSorted = true;
95
        }
96
97
        end($this->list);
98
        do {
99
            if (current($this->list) === $item) {
100
                unset($this->list[key($this->list)]);
101
102
                return;
103
            }
104
        } while (prev($this->list) !== false);
105
    }
106
107 26
    public function removePackedItems(PackedItemList $packedItemList): void
108
    {
109 26
        foreach ($packedItemList as $packedItem) {
110 26
            end($this->list);
111
            do {
112 26
                if (current($this->list) === $packedItem->getItem()) {
113 26
                    unset($this->list[key($this->list)]);
114
115 26
                    break;
116
                }
117 2
            } while (prev($this->list) !== false);
118
        }
119
    }
120
121
    /**
122
     * @internal
123
     */
124 84
    public function extract(): Item
125
    {
126 84
        if (!$this->isSorted) {
127 60
            usort($this->list, [$this->sorter, 'compare']);
128 60
            $this->list = array_reverse($this->list); // internal sort is largest at the end
129 60
            $this->isSorted = true;
130
        }
131
132 84
        return array_pop($this->list);
133
    }
134
135
    /**
136
     * @internal
137
     */
138 36
    public function top(): Item
139
    {
140 36
        if (!$this->isSorted) {
141
            usort($this->list, [$this->sorter, 'compare']);
142
            $this->list = array_reverse($this->list); // internal sort is largest at the end
143
            $this->isSorted = true;
144
        }
145
146 36
        return $this->list[array_key_last($this->list)];
147
    }
148
149
    /**
150
     * @internal
151
     * @return ItemList
152
     */
153 8
    public function topN(int $n): self
154
    {
155 8
        if (!$this->isSorted) {
156
            usort($this->list, [$this->sorter, 'compare']);
157
            $this->list = array_reverse($this->list); // internal sort is largest at the end
158
            $this->isSorted = true;
159
        }
160
161 8
        $topNList = new self();
162 8
        $topNList->list = array_slice($this->list, -$n, $n);
163 8
        $topNList->isSorted = true;
164
165 8
        return $topNList;
166
    }
167
168
    /**
169
     * @return Traversable<Item>
170
     */
171 80
    public function getIterator(): Traversable
172
    {
173 80
        if (!$this->isSorted) {
174 24
            usort($this->list, [$this->sorter, 'compare']);
175 24
            $this->list = array_reverse($this->list); // internal sort is largest at the end
176 24
            $this->isSorted = true;
177
        }
178
179 80
        return new ArrayIterator(array_reverse($this->list));
180
    }
181
182
    /**
183
     * Number of items in list.
184
     */
185 84
    public function count(): int
186
    {
187 84
        return count($this->list);
188
    }
189
190
    /**
191
     * Does this list contain items with constrained placement criteria.
192
     */
193 84
    public function hasConstrainedItems(): bool
194
    {
195 84
        if (!isset($this->hasConstrainedItems)) {
196 84
            $this->hasConstrainedItems = false;
197 84
            foreach ($this->list as $item) {
198 84
                if ($item instanceof ConstrainedPlacementItem) {
199
                    $this->hasConstrainedItems = true;
200
                    break;
201
                }
202
            }
203
        }
204
205 84
        return $this->hasConstrainedItems;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->hasConstrainedItems could return the type null which is incompatible with the type-hinted return boolean. Consider adding an additional type-check to rule them out.
Loading history...
206
    }
207
208
    /**
209
     * Does this list contain items which cannot be rotated.
210
     */
211 84
    public function hasNoRotationItems(): bool
212
    {
213 84
        if (!isset($this->hasNoRotationItems)) {
214 84
            $this->hasNoRotationItems = false;
215 84
            foreach ($this->list as $item) {
216 84
                if ($item->getAllowedRotation() === Rotation::Never) {
217
                    $this->hasNoRotationItems = true;
218
                    break;
219
                }
220
            }
221
        }
222
223 84
        return $this->hasNoRotationItems;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->hasNoRotationItems could return the type null which is incompatible with the type-hinted return boolean. Consider adding an additional type-check to rule them out.
Loading history...
224
    }
225
}
226