Passed
Push — dependabot/npm_and_yarn/docs/v... ( 8fa60d )
by
unknown
09:01
created

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