Completed
Push — master ( 3273c7...4b02cd )
by Doug
10:19
created

ItemList::insert()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

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