Completed
Push — master ( 293fdf...2451c8 )
by Igor
01:52
created

Distributor::getEligibleCodes()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 14
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 14
ccs 7
cts 7
cp 1
rs 9.2
c 0
b 0
f 0
cc 4
eloc 7
nc 3
nop 1
crap 4
1
<?php
2
3
declare(strict_types = 1);
4
5
namespace Leaditin\Distribution;
6
7
use Leaditin\Distribution\Exception\DistributorException;
8
9
/**
10
 * Class Distribution
11
 *
12
 * @package Leaditin\Distribution
13
 * @author Igor Vuckovic <[email protected]>
14
 */
15
class Distributor
16
{
17
    /** @var array */
18
    private $probabilities;
19
20
    /** @var array */
21
    private $ranges;
22
23
    /**
24
     * @param Collection $probabilities
25
     * @param int $numberOfElementsToDistribute
26
     */
27 5
    public function __construct(Collection $probabilities, int $numberOfElementsToDistribute)
28
    {
29 5
        $collection = $this->getNumerifiedCollection($probabilities, $numberOfElementsToDistribute);
30 5
        $this->probabilities = $collection->toArray();
31 5
        $this->setRanges($collection);
32 5
    }
33
34
    /**
35
     * @param array|null $excludeCodes
36
     * @return string
37
     * @throws DistributorException
38
     */
39 2
    public function useRandomCode(array $excludeCodes = null) : string
40
    {
41 2
        $eligibleCodes = $this->getEligibleCodes($excludeCodes);
42
43 2
        if (empty($eligibleCodes)) {
44 1
            throw DistributorException::exceededAllValues();
45
        }
46
47 1
        $code = $this->randomize($eligibleCodes);
48 1
        $this->probabilities[$code]--;
49
50 1
        return (string)$code;
51
    }
52
53
    /**
54
     * @param string $code
55
     * @return string
56
     * @throws DistributorException
57
     */
58 3
    public function useCode(string $code) : string
59
    {
60 3
        if (!array_key_exists($code, $this->probabilities)) {
61 1
            throw DistributorException::undefinedCode($code);
62
        }
63
64 2
        if ($this->probabilities[$code] < 1) {
65 1
            throw DistributorException::exceededValuesForCode($code);
66
        }
67
68 2
        $this->probabilities[$code]--;
69
70 2
        return $code;
71
    }
72
73
    /**
74
     * @param Collection $collection
75
     */
76 5
    private function setRanges(Collection $collection)
77
    {
78 5
        $min = 1;
79
        $sortedCollection = $this
80 5
            ->getNumerifiedCollection($collection, 100)
81 5
            ->getSortedCollection();
82
83 5
        foreach ($sortedCollection as $element) {
84 4
            if ($element->getValue() === 0.00) {
85 1
                continue;
86
            }
87
88 4
            $max = $min + $element->getValue() - 1;
89 4
            $this->ranges[$element->getCode()] = ['min' => $min, 'max' => $max];
90 4
            $min = $max + 1;
91
        }
92 5
    }
93
94
95
    /**
96
     * @param array|null $excludeCodes
97
     * @return array
98
     */
99 2
    private function getEligibleCodes(array $excludeCodes = null) : array
100
    {
101 2
        $eligibleCodes = [];
102
103 2
        foreach ($this->probabilities as $code => $value) {
104 1
            if ($value <= 0 || in_array($code, (array)$excludeCodes, true)) {
105 1
                continue;
106
            }
107
108 1
            $eligibleCodes[$code] = $value;
109
        }
110
111 2
        return $eligibleCodes;
112
    }
113
114
    /**
115
     * @param array $possibleCodes
116
     * @return string
117
     */
118 1
    private function randomize(array $possibleCodes) : string
119
    {
120 1
        $code = null;
121 1
        $probabilityRanges = array_intersect_key($this->ranges, $possibleCodes);
122
123 1
        while (true) {
124 1
            $int = random_int(1, 100);
125 1
            foreach ($probabilityRanges as $code => $range) {
126 1
                if ($int >= $range['min'] && $int <= $range['max']) {
127 1
                    break 2;
128
                }
129
            }
130
        }
131
132 1
        return (string)$code;
133
    }
134
135
    /**
136
     * @param Collection $probabilities
137
     * @param int $numberOfElementsToDistribute
138
     * @return Collection
139
     */
140 5
    private function getNumerifiedCollection(Collection $probabilities, int $numberOfElementsToDistribute) : Collection
141
    {
142 5
        $elements = [];
143
        $collection = $probabilities
144 5
            ->getToppedUpCollection()
145 5
            ->getSortedCollection()
146 5
            ->getReversedCollection();
147
148 5
        foreach ($collection as $element) {
149 5
            $elements[] = new Element(
150 5
                $element->getCode(),
151 5
                $this->deriveNumericValue($element, $numberOfElementsToDistribute)
152
            );
153
        }
154
155 5
        $elements = array_slice($elements, 0, $numberOfElementsToDistribute);
156
157 5
        return new Collection(...$elements);
158
    }
159
160
    /**
161
     * @param Element $element
162
     * @param int $numberOfElementsToDistribute
163
     * @return float
164
     */
165 5
    private function deriveNumericValue(Element $element, int $numberOfElementsToDistribute) : float
166
    {
167 5
        if ($element->getValue() === 0.00) {
168 1
            return 0;
169
        }
170
171 5
        $value = $numberOfElementsToDistribute * $element->getValue() / 100;
172
173 5
        return $value < 1 ? 1 : round($value);
174
    }
175
}
176