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

Anytime::fillPartitions()   A

Complexity

Conditions 6
Paths 14

Size

Total Lines 34
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 18
nc 14
nop 3
dl 0
loc 34
rs 9.0444
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
use drupol\phpermutations\Generators\Combinations;
10
11
/**
12
 * Class Anytime.
13
 */
14
class Anytime extends Partitioner
15
{
16
    /**
17
     * @var \ArrayObject
18
     */
19
    protected $dataset;
20
21
    /**
22
     * @var int
23
     */
24
    private $timeout;
25
26
    /**
27
     * Linear constructor.
28
     */
29
    public function __construct(int $timeout = 10)
30
    {
31
        $this->dataset = new \ArrayObject();
32
        $this->timeout = $timeout;
33
    }
34
35
    /**
36
     * {@inheritdoc}
37
     */
38
    public function add(...$values)
39
    {
40
        foreach ($values as $value) {
41
            $this->dataset->append($value);
42
        }
43
44
        return $this;
45
    }
46
47
    /**
48
     * @return int
49
     */
50
    public function getTimeout()
51
    {
52
        return $this->timeout;
53
    }
54
55
    /**
56
     * @param int $timeout
57
     *
58
     * @return $this
59
     */
60
    public function setTimeout(int $timeout)
61
    {
62
        $this->timeout = $timeout;
63
64
        return $this;
65
    }
66
67
    /**
68
     * {@inheritdoc}
69
     */
70
    protected function fillPartitions(Partitions $partitions, array $dataset, int $chunks): void
71
    {
72
        $partition = new Partition($dataset);
73
        $partition->uasort(function ($left, $right) {
74
            return $left->getWeight() <=> $right->getWeight();
75
        });
76
        $dataset = $partition->getArrayCopy();
77
78
        for ($p = $chunks; 1 < $p; --$p) {
79
            $partition = new Partition($dataset);
80
81
            $best = $partition->getWeight();
82
            $target = $best / $p;
83
            $maxSize = \floor($partition->count() / $p);
84
85
            $goodSubset = $this->getSubsets($maxSize, $dataset, $target, $best);
86
87
            // We cannot use array_udiff() to compare objects because we only need
88
            // to remove one object at a time.
89
            foreach ($goodSubset as $item) {
90
                if (false !== $key = \array_search($item, $dataset, true)) {
91
                    unset($dataset[$key]);
92
                }
93
            }
94
95
            if (!empty($goodSubset)) {
96
                $partitionsArray[] = $goodSubset;
97
            }
98
        }
99
100
        $partitionsArray[] = $dataset;
101
102
        foreach ($partitionsArray as $key => $subset) {
103
            $partitions->partition($key)->exchangeArray($subset);
104
        }
105
    }
106
107
    /**
108
     * @param float $maxSize
109
     * @param array $dataset
110
     * @param float $target
111
     * @param float $best
112
     *
113
     * @return array
114
     */
115
    private function getSubsets($maxSize, $dataset, $target, $best)
116
    {
117
        $start_time = \time();
118
        $goodSubsets = $dataset;
119
        $timeout = $this->getTimeout();
120
121
        for ($i = 1; $i <= $maxSize; ++$i) {
122
            $permutations = new Combinations($dataset, $i);
123
            foreach ($permutations->generator() as $subset) {
124
                if (\time() - $start_time > $timeout) {
125
                    return $goodSubsets;
126
                }
127
128
                $x = 0;
129
                foreach ($subset as $item) {
130
                    $x += $item->getWeight();
131
                    if (\abs($x - $target) < \abs($best - $target)) {
132
                        $best = $x;
133
                        $goodSubsets = $subset;
134
                    }
135
                }
136
            }
137
        }
138
139
        return $goodSubsets;
140
    }
141
}
142