Passed
Push — 3.x ( 0eaa4e...6497e2 )
by Doug
01:43
created

BoxList::getIterator()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 0
dl 0
loc 8
ccs 5
cts 5
cp 1
crap 2
rs 10
c 0
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 IteratorAggregate;
13
use Traversable;
14
use function usort;
15
16
/**
17
 * List of boxes available to put items into, ordered by volume.
18
 *
19
 * @author Doug Wright
20
 */
21
class BoxList implements IteratorAggregate
22
{
23
    /**
24
     * List containing boxes.
25
     *
26
     * @var Box[]
27
     */
28
    private $list = [];
29
30
    /**
31
     * Has this list already been sorted?
32
     *
33
     * @var bool
34
     */
35
    private $isSorted = false;
36
37
    /**
38
     * Do a bulk create.
39
     *
40
     * @param Box[] $boxes
41
     *
42
     * @return BoxList
43
     */
44 2
    public static function fromArray(array $boxes, bool $preSorted = false): self
45
    {
46 2
        $list = new static();
47 2
        $list->list = $boxes;
48 2
        $list->isSorted = $preSorted;
49
50 2
        return $list;
51
    }
52
53
    /**
54
     * @return Traversable|Box[]
55
     */
56 34
    public function getIterator(): Traversable
57
    {
58 34
        if (!$this->isSorted) {
59 33
            usort($this->list, [$this, 'compare']);
60 33
            $this->isSorted = true;
61
        }
62
63 34
        return new ArrayIterator($this->list);
64
    }
65
66 31
    public function insert(Box $item): void
67
    {
68 31
        $this->list[] = $item;
69 31
    }
70
71
    /**
72
     * @param Box $boxA
73
     * @param Box $boxB
74
     */
75 17
    public static function compare($boxA, $boxB): int
76
    {
77 17
        $boxAVolume = $boxA->getInnerWidth() * $boxA->getInnerLength() * $boxA->getInnerDepth();
78 17
        $boxBVolume = $boxB->getInnerWidth() * $boxB->getInnerLength() * $boxB->getInnerDepth();
79
80 17
        $volumeDecider = $boxAVolume <=> $boxBVolume; // try smallest box first
81
82 17
        if ($volumeDecider !== 0) {
83 13
            return $volumeDecider;
84
        }
85
86 4
        $emptyWeightDecider = $boxA->getEmptyWeight() <=> $boxB->getEmptyWeight(); // with smallest empty weight
87 4
        if ($emptyWeightDecider !== 0) {
88 4
            return $emptyWeightDecider;
89
        }
90
91
        // maximum weight capacity as fallback decider
92 1
        return ($boxA->getMaxWeight() - $boxA->getEmptyWeight()) <=> ($boxB->getMaxWeight() - $boxB->getEmptyWeight());
93
    }
94
}
95