Passed
Push — master ( b3e52b...de1ca6 )
by Doug
11:09
created

ItemList::remove()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 3

Importance

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