Completed
Push — master ( 9c377f...ca0093 )
by Doug
10:37
created

ItemList   A

Complexity

Total Complexity 18

Size/Duplication

Total Lines 150
Duplicated Lines 0 %

Test Coverage

Coverage 97.96%

Importance

Changes 8
Bugs 1 Features 0
Metric Value
eloc 40
c 8
b 1
f 0
dl 0
loc 150
ccs 48
cts 49
cp 0.9796
rs 10
wmc 18

9 Methods

Rating   Name   Duplication   Size   Complexity  
A fromArray() 0 7 1
A remove() 0 6 3
A insert() 0 3 1
A extract() 0 8 2
A top() 0 12 3
A getIterator() 0 8 2
A compare() 0 12 3
A topN() 0 12 2
A count() 0 3 1
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
            return array_slice($this->list, -1, 1)[0];
112
        }
113
114 48
        return $this->list[array_key_last($this->list)];
115
    }
116
117
    /**
118
     * @internal
119
     *
120
     * @param  int      $n
121
     * @return ItemList
122
     */
123 15
    public function topN(int $n): self
124
    {
125 15
        if (!$this->isSorted) {
126 1
            usort($this->list, [$this, 'compare']);
127 1
            $this->isSorted = true;
128
        }
129
130 15
        $topNList = new self();
131 15
        $topNList->list = array_slice($this->list, -$n, $n);
132 15
        $topNList->isSorted = true;
133
134 15
        return $topNList;
135
    }
136
137
    /**
138
     * @return Traversable
139
     */
140 50
    public function getIterator(): Traversable
141
    {
142 50
        if (!$this->isSorted) {
143 4
            usort($this->list, [$this, 'compare']);
144 4
            $this->isSorted = true;
145
        }
146
147 50
        return new ArrayIterator(array_reverse($this->list));
148
    }
149
150
    /**
151
     * Number of items in list.
152
     *
153
     * @return int
154
     */
155 53
    public function count(): int
156
    {
157 53
        return count($this->list);
158
    }
159
160
    /**
161
     * @param Item $itemA
162
     * @param Item $itemB
163
     *
164
     * @return int
165
     */
166 49
    private function compare(Item $itemA, Item $itemB): int
167
    {
168 49
        $volumeDecider = $itemA->getWidth() * $itemA->getLength() * $itemA->getDepth() <=> $itemB->getWidth() * $itemB->getLength() * $itemB->getDepth();
169 49
        if ($volumeDecider !== 0) {
170 26
            return $volumeDecider;
171
        }
172 43
        $weightDecider = $itemA->getWeight() - $itemB->getWeight();
173 43
        if ($weightDecider !== 0) {
174 3
            return $weightDecider;
175
        }
176
177 40
        return $itemB->getDescription() <=> $itemA->getDescription();
178
    }
179
}
180