Test Failed
Pull Request — master (#198)
by
unknown
05:14
created

ItemList::remove()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 15
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 4

Importance

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