Completed
Push — master ( ab3db1...67bebb )
by Doug
28:56
created

ItemList::topN()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2.0625

Importance

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