Passed
Push — master ( 2cb142...ce8075 )
by Doug
07:09
created

ItemList::compare()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 3

Importance

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