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