Completed
Push — test_jit ( c34f6e...87859e )
by Doug
10:03
created

ItemList::extract()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 0
dl 0
loc 8
ccs 5
cts 5
cp 1
crap 2
rs 10
c 0
b 0
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 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
     * @return ItemList
56
     */
57 55
    public static function fromArray(array $items, bool $preSorted = false): self
58
    {
59 55
        $list = new static();
60 55
        $list->list = array_reverse($items); // internal sort is largest at the end
61 55
        $list->isSorted = $preSorted;
62
63 55
        return $list;
64
    }
65
66 64
    public function insert(Item $item): void
67
    {
68 64
        $this->list[] = $item;
69 64
        $this->isSorted = false;
70 64
        $this->hasConstrainedItems = $this->hasConstrainedItems || $item instanceof ConstrainedPlacementItem;
71 64
    }
72
73
    /**
74
     * Remove item from list.
75
     */
76 45
    public function remove(Item $item): void
77
    {
78 45
        if (!$this->isSorted) {
79 3
            usort($this->list, [$this, 'compare']);
80 3
            $this->isSorted = true;
81
        }
82
83 45
        end($this->list);
84
        do {
85 45
            if (current($this->list) === $item) {
86 45
                unset($this->list[key($this->list)]);
87
88 45
                return;
89
            }
90 6
        } while (prev($this->list) !== false);
91
    }
92
93
    /**
94
     * @internal
95
     */
96 58
    public function extract(): Item
97
    {
98 58
        if (!$this->isSorted) {
99 30
            usort($this->list, [$this, 'compare']);
100 30
            $this->isSorted = true;
101
        }
102
103 58
        return array_pop($this->list);
104
    }
105
106
    /**
107
     * @internal
108
     */
109 43
    public function top(): Item
110
    {
111 43
        if (!$this->isSorted) {
112 1
            usort($this->list, [$this, 'compare']);
113 1
            $this->isSorted = true;
114
        }
115
116 43
        if (\PHP_VERSION_ID < 70300) {
117
            return array_slice($this->list, -1, 1)[0];
118
        }
119
120 43
        return $this->list[array_key_last($this->list)];
121
    }
122
123
    /**
124
     * @internal
125
     *
126
     * @return ItemList
127
     */
128 36
    public function topN(int $n): self
129
    {
130 36
        if (!$this->isSorted) {
131 1
            usort($this->list, [$this, 'compare']);
132 1
            $this->isSorted = true;
133
        }
134
135 36
        $topNList = new self();
136 36
        $topNList->list = array_slice($this->list, -$n, $n);
137 36
        $topNList->isSorted = true;
138
139 36
        return $topNList;
140
    }
141
142 61
    public function getIterator(): Traversable
143
    {
144 61
        if (!$this->isSorted) {
145 31
            usort($this->list, [$this, 'compare']);
146 31
            $this->isSorted = true;
147
        }
148
149 61
        return new ArrayIterator(array_reverse($this->list));
150
    }
151
152
    /**
153
     * Number of items in list.
154
     */
155 61
    public function count(): int
156
    {
157 61
        return count($this->list);
158
    }
159
160
    /**
161
     * Does this list contain items with constrained placement criteria.
162
     */
163 56
    public function hasConstrainedItems(): bool
164
    {
165 56
        if (!isset($this->hasConstrainedItems)) {
166 34
            $this->hasConstrainedItems = false;
167 34
            foreach ($this->list as $item) {
168 34
                if ($item instanceof ConstrainedPlacementItem) {
169
                    $this->hasConstrainedItems = true;
170
                    break;
171
                }
172
            }
173
        }
174
175 56
        return $this->hasConstrainedItems;
176
    }
177
178 59
    private static function compare(Item $itemA, Item $itemB): int
179
    {
180 59
        $volumeDecider = $itemA->getWidth() * $itemA->getLength() * $itemA->getDepth() <=> $itemB->getWidth() * $itemB->getLength() * $itemB->getDepth();
181 59
        if ($volumeDecider !== 0) {
182 29
            return $volumeDecider;
183
        }
184 52
        $weightDecider = $itemA->getWeight() - $itemB->getWeight();
185 52
        if ($weightDecider !== 0) {
186 4
            return $weightDecider;
187
        }
188
189 49
        return $itemB->getDescription() <=> $itemA->getDescription();
190
    }
191
}
192