Passed
Push — master ( 0fef05...2dccfb )
by Doug
08:06
created

ItemList::top()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3.0261

Importance

Changes 4
Bugs 1 Features 0
Metric Value
cc 3
eloc 6
c 4
b 1
f 0
nc 4
nop 0
dl 0
loc 12
ccs 6
cts 7
cp 0.8571
crap 3.0261
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 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
     * Do a bulk create.
46
     *
47
     * @param  Item[]   $items
48
     * @return ItemList
49
     */
50 48
    public static function fromArray(array $items, bool $preSorted = false): self
51
    {
52 48
        $list = new static();
53 48
        $list->list = array_reverse($items); // internal sort is largest at the end
54 48
        $list->isSorted = $preSorted;
55
56 48
        return $list;
57
    }
58
59 56
    public function insert(Item $item): void
60
    {
61 56
        $this->list[] = $item;
62 56
    }
63
64
    /**
65
     * Remove item from list.
66
     */
67 35
    public function remove(Item $item): void
68
    {
69 35
        if (!$this->isSorted) {
70 3
            usort($this->list, [$this, 'compare']);
71 3
            $this->isSorted = true;
72
        }
73
74 35
        end($this->list);
75
        do {
76 35
            if (current($this->list) === $item) {
77 35
                unset($this->list[key($this->list)]);
78
79 35
                return;
80
            }
81 7
        } while (prev($this->list) !== false);
82
83
    }
84
85
    /**
86
     * @internal
87
     */
88 50
    public function extract(): Item
89
    {
90 50
        if (!$this->isSorted) {
91 27
            usort($this->list, [$this, 'compare']);
92 27
            $this->isSorted = true;
93
        }
94
95 50
        return array_pop($this->list);
96
    }
97
98
    /**
99
     * @internal
100
     */
101 47
    public function top(): Item
102
    {
103 47
        if (!$this->isSorted) {
104 1
            usort($this->list, [$this, 'compare']);
105 1
            $this->isSorted = true;
106
        }
107
108 47
        if (\PHP_VERSION_ID < 70300) {
109
            return array_slice($this->list, -1, 1)[0];
110
        }
111
112 47
        return $this->list[array_key_last($this->list)];
113
    }
114
115
    /**
116
     * @internal
117
     *
118
     * @return ItemList
119
     */
120 17
    public function topN(int $n): self
121
    {
122 17
        if (!$this->isSorted) {
123 1
            usort($this->list, [$this, 'compare']);
124 1
            $this->isSorted = true;
125
        }
126
127 17
        $topNList = new self();
128 17
        $topNList->list = array_slice($this->list, -$n, $n);
129 17
        $topNList->isSorted = true;
130
131 17
        return $topNList;
132
    }
133
134 53
    public function getIterator(): Traversable
135
    {
136 53
        if (!$this->isSorted) {
137 26
            usort($this->list, [$this, 'compare']);
138 26
            $this->isSorted = true;
139
        }
140
141 53
        return new ArrayIterator(array_reverse($this->list));
142
    }
143
144
    /**
145
     * Number of items in list.
146
     */
147 53
    public function count(): int
148
    {
149 53
        return count($this->list);
150
    }
151
152 51
    private function compare(Item $itemA, Item $itemB): int
153
    {
154 51
        $volumeDecider = $itemA->getWidth() * $itemA->getLength() * $itemA->getDepth() <=> $itemB->getWidth() * $itemB->getLength() * $itemB->getDepth();
155 51
        if ($volumeDecider !== 0) {
156 27
            return $volumeDecider;
157
        }
158 45
        $weightDecider = $itemA->getWeight() - $itemB->getWeight();
159 45
        if ($weightDecider !== 0) {
160 4
            return $weightDecider;
161
        }
162
163 42
        return $itemB->getDescription() <=> $itemA->getDescription();
164
    }
165
}
166