Passed
Push — 3.x ( 0f93e4...7d0e6c )
by Doug
03:42
created

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