Passed
Pull Request — master (#2)
by Pol
03:09
created

Linear::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types = 1);
4
5
namespace drupol\phpartition;
6
7
use drupol\phpartition\Partition\Partition;
8
use drupol\phpartition\Partitions\Partitions;
9
10
/**
11
 * Class Linear.
12
 */
13
class Linear extends Partitioner
14
{
15
    /**
16
     * @var \ArrayObject
17
     */
18
    protected $dataset;
19
20
    /**
21
     * Linear constructor.
22
     */
23
    public function __construct()
24
    {
25
        $this->dataset = new \ArrayObject();
26
    }
27
28
    /**
29
     * {@inheritdoc}
30
     */
31
    public function add(...$values)
32
    {
33
        foreach ($values as $value) {
34
            $this->dataset->append($value);
35
        }
36
37
        return $this;
38
    }
39
40
    /**
41
     * @param \drupol\phpartition\Partitions\Partitions $partitions
42
     * @param array $dataset
43
     * @param int $chunks
44
     */
45
    protected function fillPartitions(Partitions $partitions, array $dataset, int $chunks): void
46
    {
47
        // An array S of non-negative numbers {s1, ... ,sn}
48
        $s = \array_merge([null], \array_values($dataset)); // adapt indices here: [0..n-1] => [1..n]
49
        // Integer K - number of ranges to split items into
50
        $k = $chunks;
51
        $n = \count($dataset);
52
        // Let M[n,k] be the minimum possible cost over all partitionings of N elements to K ranges
53
        $m = [];
54
        // Let D[n,k] be the position of K-th divider
55
        // which produces the minimum possible cost partitioning of N elements to K ranges
56
        $d = [];
57
        // Note: For code simplicity we don't use zero indices for `m` and `d`
58
        //       to make code match math formulas
59
        // Let pi be the sum of first i elements (cost calculation optimization)
60
        $p = [];
61
        // 1) Init prefix sums array
62
        //    pi = sum of {s1, ..., si}
63
        $p[0] = $this->getPartitionItemFactory()::create(0);
64
        for ($i = 1; $i <= $n; ++$i) {
65
            $p[$i] = $this->getPartitionItemFactory()::create($p[$i - 1]->getWeight() + $s[$i]->getWeight());
66
        }
67
        // 2) Init boundaries
68
        for ($i = 1; $i <= $n; ++$i) {
69
            // The only possible partitioning of i elements to 1 range is a single all-elements range
70
            // The cost of that partitioning is the sum of those i elements
71
            $m[$i][1] = $p[$i]; // sum of {s1, ..., si} -- optimized using pi
72
        }
73
        for ($j = 1; $j <= $k; ++$j) {
74
            // The only possible partitioning of 1 element into j ranges is a single one-element range
75
            // The cost of that partitioning is the value of first element
76
            $m[1][$j] = $s[1];
77
        }
78
        // 3) Main recurrence (fill the rest of values in table M)
79
        for ($i = 2; $i <= $n; ++$i) {
80
            for ($j = 2; $j <= $k; ++$j) {
81
                $solutions = [];
82
                for ($x = 1; ($i - 1) >= $x; ++$x) {
83
                    $solutions[] = [
84
                        0 => $this->getPartitionItemFactory()::create(
85
                            \max(
86
                                $m[$x][$j - 1]->getWeight(),
87
                                $p[$i]->getWeight() - $p[$x]->getWeight()
88
                            )
89
                        ),
90
                        1 => $x,
91
                    ];
92
                }
93
94
                \usort(
95
                    $solutions,
96
                    static function (array $x, array $y) {
97
                        return $x[0] <=> $y[0];
98
                    }
99
                );
100
101
                $best_solution = $solutions[0];
102
                $m[$i][$j] = $best_solution[0];
103
                $d[$i][$j] = $best_solution[1];
104
            }
105
        }
106
107
        // 4) Reconstruct partitioning
108
        $i = $n;
109
        $j = $k;
110
        $partition = [];
111
        while (0 < $j) {
112
            // delimiter position
113
            $dp = $d[$i][$j] ?? 0;
114
            // Add elements after delimiter {sdp, ..., si} to resulting $partition.
115
            $partition[] = \array_slice($s, $dp + 1, $i - $dp);
116
            // Step forward: look for delimiter position for partitioning M[$dp, $j-1]
117
            $i = $dp;
118
            --$j;
119
        }
120
121
        // Fix order as we reconstructed the partitioning from end to start
122
        $partition = \array_reverse($partition);
123
124
        foreach ($partition as $i => $p) {
125
            $partitions->partition($i)->exchangeArray($p);
126
        }
127
    }
128
}
129