Passed
Push — 3.x ( 071761...f78397 )
by Doug
03:06
created

ItemList::__construct()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 2

Importance

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