Passed
Pull Request — master (#294)
by
unknown
18:38 queued 14:21
created

ItemList::extract()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 5
c 0
b 0
f 0
nc 2
nop 0
dl 0
loc 9
ccs 6
cts 6
cp 1
crap 2
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
use ArrayIterator;
16
use function count;
17
use Countable;
18
use function current;
19
use function end;
20
use IteratorAggregate;
21
use function key;
22
use function prev;
23
use Traversable;
24
use function usort;
25
26
/**
27
 * List of items to be packed, ordered by volume.
28
 */
29
class ItemList implements Countable, IteratorAggregate
30
{
31
    /** @var Item[] */
32
    private array $list = [];
33
34
    private bool $isSorted = false;
35
36
    private ItemSorter $sorter;
37
38
    private ?bool $hasConstrainedItems = null;
39
40
    private ?bool $hasNoRotationItems = null;
41
42 80
    public function __construct(?ItemSorter $sorter = null)
43
    {
44 80
        $this->sorter = $sorter ?: new DefaultItemSorter();
45
    }
46
47
    /**
48
     * Do a bulk create.
49
     *
50
     * @param  Item[]   $items
51
     * @return ItemList
52
     */
53 76
    public static function fromArray(array $items, bool $preSorted = false): self
54
    {
55 76
        $list = new self();
56 76
        $list->list = array_reverse($items); // internal sort is largest at the end
57 76
        $list->isSorted = $preSorted;
58
59 76
        return $list;
60
    }
61
62 84
    public function insert(Item $item, int $qty = 1): void
63
    {
64 84
        for ($i = 0; $i < $qty; ++$i) {
65 84
            $this->list[] = $item;
66
        }
67 84
        $this->isSorted = false;
68
69 84
        if (isset($this->hasConstrainedItems)) { // normally lazy evaluated, override if that's already been done
70
            $this->hasConstrainedItems = $this->hasConstrainedItems || $item instanceof ConstrainedPlacementItem;
71
        }
72
73 84
        if (isset($this->hasNoRotationItems)) { // normally lazy evaluated, override if that's already been done
74
            $this->hasNoRotationItems = $this->hasNoRotationItems || $item->getAllowedRotation() === Rotation::Never;
0 ignored issues
show
Bug introduced by
The type DVDoug\BoxPacker\Rotation was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
75
        }
76
    }
77
78
    /**
79
     * Remove item from list.
80
     */
81
    public function remove(Item $item): void
82
    {
83
        if (!$this->isSorted) {
84
            usort($this->list, [$this->sorter, 'compare']);
85
            $this->list = array_reverse($this->list); // internal sort is largest at the end
86
            $this->isSorted = true;
87
        }
88
89
        end($this->list);
90
        do {
91
            if (current($this->list) === $item) {
92
                unset($this->list[key($this->list)]);
93
94
                return;
95
            }
96
        } while (prev($this->list) !== false);
97
    }
98
99 26
    public function removePackedItems(PackedItemList $packedItemList): void
100
    {
101 26
        foreach ($packedItemList as $packedItem) {
102 26
            end($this->list);
103
            do {
104 26
                if (current($this->list) === $packedItem->getItem()) {
105 26
                    unset($this->list[key($this->list)]);
106
107 26
                    break;
108
                }
109 2
            } while (prev($this->list) !== false);
110
        }
111
    }
112
113
    /**
114
     * @internal
115
     */
116 84
    public function extract(): Item
117
    {
118 84
        if (!$this->isSorted) {
119 60
            usort($this->list, [$this->sorter, 'compare']);
120 60
            $this->list = array_reverse($this->list); // internal sort is largest at the end
121 60
            $this->isSorted = true;
122
        }
123
124 84
        return array_pop($this->list);
125
    }
126
127
    /**
128
     * @internal
129
     */
130 36
    public function top(): Item
131
    {
132 36
        if (!$this->isSorted) {
133
            usort($this->list, [$this->sorter, 'compare']);
134
            $this->list = array_reverse($this->list); // internal sort is largest at the end
135
            $this->isSorted = true;
136
        }
137
138 36
        return $this->list[array_key_last($this->list)];
139
    }
140
141
    /**
142
     * @internal
143
     * @return ItemList
144
     */
145 8
    public function topN(int $n): self
146
    {
147 8
        if (!$this->isSorted) {
148
            usort($this->list, [$this->sorter, 'compare']);
149
            $this->list = array_reverse($this->list); // internal sort is largest at the end
150
            $this->isSorted = true;
151
        }
152
153 8
        $topNList = new self();
154 8
        $topNList->list = array_slice($this->list, -$n, $n);
155 8
        $topNList->isSorted = true;
156
157 8
        return $topNList;
158
    }
159
160
    /**
161
     * @return Traversable<Item>
162
     */
163 80
    public function getIterator(): Traversable
164
    {
165 80
        if (!$this->isSorted) {
166 24
            usort($this->list, [$this->sorter, 'compare']);
167 24
            $this->list = array_reverse($this->list); // internal sort is largest at the end
168 24
            $this->isSorted = true;
169
        }
170
171 80
        return new ArrayIterator(array_reverse($this->list));
172
    }
173
174
    /**
175
     * Number of items in list.
176
     */
177 84
    public function count(): int
178
    {
179 84
        return count($this->list);
180
    }
181
182
    /**
183
     * Does this list contain items with constrained placement criteria.
184
     */
185 84
    public function hasConstrainedItems(): bool
186
    {
187 84
        if (!isset($this->hasConstrainedItems)) {
188 84
            $this->hasConstrainedItems = false;
189 84
            foreach ($this->list as $item) {
190 84
                if ($item instanceof ConstrainedPlacementItem) {
191
                    $this->hasConstrainedItems = true;
192
                    break;
193
                }
194
            }
195
        }
196
197 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...
198
    }
199
200
    /**
201
     * Does this list contain items which cannot be rotated.
202
     */
203 84
    public function hasNoRotationItems(): bool
204
    {
205 84
        if (!isset($this->hasNoRotationItems)) {
206 84
            $this->hasNoRotationItems = false;
207 84
            foreach ($this->list as $item) {
208 84
                if ($item->getAllowedRotation() === Rotation::Never) {
209
                    $this->hasNoRotationItems = true;
210
                    break;
211
                }
212
            }
213
        }
214
215 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...
216
    }
217
}
218