Completed
Push — issue_187 ( fb1b49...8952f9 )
by Doug
158:46 queued 155:58
created

ItemList::top()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 3.7085

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 4
cts 7
cp 0.5714
crap 3.7085
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 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 23
    public function insert(Item $item): void
60
    {
61 23
        $this->list[] = $item;
62 23
    }
63
64
    /**
65
     * Remove item from list.
66
     */
67 10
    public function remove(Item $item): void
68
    {
69 10
        if (!$this->isSorted) {
70 2
            usort($this->list, [$this, 'compare']);
71 2
            $this->isSorted = true;
72
        }
73
74 10
        end($this->list);
75
        do {
76 10
            if (current($this->list) === $item) {
77 10
                unset($this->list[key($this->list)]);
78
79 10
                return;
80
            }
81 1
        } while (prev($this->list) !== false);
82
    }
83
84
    /**
85
     * @internal
86
     */
87 22
    public function extract(): Item
88
    {
89 22
        if (!$this->isSorted) {
90 15
            usort($this->list, [$this, 'compare']);
91 15
            $this->isSorted = true;
92
        }
93
94 22
        return array_pop($this->list);
95
    }
96
97
    /**
98
     * @internal
99
     */
100 21
    public function top(): Item
101
    {
102 21
        if (!$this->isSorted) {
103
            usort($this->list, [$this, 'compare']);
104
            $this->isSorted = true;
105
        }
106
107 21
        if (\PHP_VERSION_ID < 70300) {
108
            return array_slice($this->list, -1, 1)[0];
109
        }
110
111 21
        return $this->list[array_key_last($this->list)];
112
    }
113
114
    /**
115
     * @internal
116
     *
117
     * @return ItemList
118
     */
119 3
    public function topN(int $n): self
120
    {
121 3
        if (!$this->isSorted) {
122
            usort($this->list, [$this, 'compare']);
123
            $this->isSorted = true;
124
        }
125
126 3
        $topNList = new self();
127 3
        $topNList->list = array_slice($this->list, -$n, $n);
128 3
        $topNList->isSorted = true;
129
130 3
        return $topNList;
131
    }
132
133 23
    public function getIterator(): Traversable
134
    {
135 23
        if (!$this->isSorted) {
136 8
            usort($this->list, [$this, 'compare']);
137 8
            $this->isSorted = true;
138
        }
139
140 23
        return new ArrayIterator(array_reverse($this->list));
141
    }
142
143
    /**
144
     * Number of items in list.
145
     */
146 23
    public function count(): int
147
    {
148 23
        return count($this->list);
149
    }
150
151 22
    private function compare(Item $itemA, Item $itemB): int
152
    {
153 22
        $volumeDecider = $itemA->getWidth() * $itemA->getLength() * $itemA->getDepth() <=> $itemB->getWidth() * $itemB->getLength() * $itemB->getDepth();
154 22
        if ($volumeDecider !== 0) {
155 13
            return $volumeDecider;
156
        }
157 20
        $weightDecider = $itemA->getWeight() - $itemB->getWeight();
158 20
        if ($weightDecider !== 0) {
159 2
            return $weightDecider;
160
        }
161
162 18
        return $itemB->getDescription() <=> $itemA->getDescription();
163
    }
164
}
165