Distributor::getRandomCodeFromRanges()   A
last analyzed

Complexity

Conditions 4
Paths 3

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 12
ccs 6
cts 6
cp 1
rs 9.2
c 0
b 0
f 0
cc 4
eloc 6
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
        $probabilityRanges = array_intersect_key($this->ranges, $possibleCodes);
121
122
        do {
123 1
            $code = $this->getRandomCodeFromRanges($probabilityRanges);
124 1
        } while ($code === false);
125
126 1
        return (string)$code;
127
    }
128
129
    /**
130
     * @param array $ranges
131
     * @return bool|string
132
     */
133 1
    private function getRandomCodeFromRanges(array $ranges)
134
    {
135 1
        $int = random_int(1, 100);
136
137 1
        foreach ($ranges as $code => $range) {
138 1
            if ($int >= $range['min'] && $int <= $range['max']) {
139 1
                return $code;
140
            }
141
        }
142
143 1
        return false;
144
    }
145
146
    /**
147
     * @param Collection $probabilities
148
     * @param int $numberOfElementsToDistribute
149
     * @return Collection
150
     */
151 5
    private function getNumerifiedCollection(Collection $probabilities, int $numberOfElementsToDistribute) : Collection
152
    {
153 5
        $elements = [];
154
        $collection = $probabilities
155 5
            ->getToppedUpCollection()
156 5
            ->getSortedCollection()
157 5
            ->getReversedCollection();
158
159 5
        foreach ($collection as $element) {
160 5
            $elements[] = new Element(
161 5
                $element->getCode(),
162 5
                $this->deriveNumericValue($element, $numberOfElementsToDistribute)
163
            );
164
        }
165
166 5
        $elements = array_slice($elements, 0, $numberOfElementsToDistribute);
167
168 5
        return new Collection(...$elements);
169
    }
170
171
    /**
172
     * @param Element $element
173
     * @param int $numberOfElementsToDistribute
174
     * @return float
175
     */
176 5
    private function deriveNumericValue(Element $element, int $numberOfElementsToDistribute) : float
177
    {
178 5
        if ($element->getValue() === 0.00) {
179 1
            return 0;
180
        }
181
182 5
        $value = $numberOfElementsToDistribute * $element->getValue() / 100;
183
184 5
        return $value < 1 ? 1 : round($value);
185
    }
186
}
187