122 public function setLimits($lowerLimit =
null, $upperLimit =
null)
124 $this->_lowerLimit = empty($lowerLimit) ? null : (double)$lowerLimit;
125 $this->_upperLimit = empty($upperLimit) ? null : (double)$upperLimit;
142 $this->_minValue = $min;
143 $this->_maxValue = $max;
144 $valueRange = $max - $min;
145 if (
$count < 2 || $valueRange <= 0) {
147 $this->_intervalsNumber = 1;
152 if ($standardDeviation <= 0) {
153 $intervalsNumber = pow(10, self::TEN_POWER_ROUNDING_FACTOR);
155 $intervalsNumber = $valueRange * pow(
$count, 1 / 3) / (3.5 * $standardDeviation);
157 $this->_intervalsNumber = max(ceil($intervalsNumber), self::MIN_INTERVALS_NUMBER);
158 $this->_intervalsNumber = (int)min($this->_intervalsNumber, self::MAX_INTERVALS_NUMBER);
178 for ($intervalNumber = 1; $intervalNumber < $this->
getIntervalsNumber(); ++$intervalNumber) {
180 if (empty($separator)) {
183 if ($this->_quantileInterval[0] == 0) {
184 $intervalFirstValue = $this->_values[0];
186 $separatorCandidate =
false;
187 $newIntervalFirstValue = $intervalFirstValue;
188 $newLastSeparator = $lastSeparator;
191 while (!empty($separator) && !array_key_exists($intervalNumber,
$result)) {
192 $separatorsPortion = array_shift($separator);
194 if ($bestSeparator && $bestSeparator[2] > 0) {
195 $isEqualValue = $intervalFirstValue ==
196 $this->_values[$bestSeparator[2] - 1] ? $this->_values[0] :
false;
197 $count = $bestSeparator[2] + $this->_quantileInterval[0] - $lastCount;
199 'from' => $isEqualValue !==
false ? $isEqualValue : $lastSeparator,
200 'to' => $isEqualValue !==
false ? $isEqualValue : $bestSeparator[1],
203 if (abs(1 -
$count / $valuesPerInterval) <= self::INTERVAL_DEFLECTION_LIMIT) {
204 $newLastSeparator = $bestSeparator[1];
205 $newIntervalFirstValue = $this->_values[$bestSeparator[2]];
206 $result[$intervalNumber] = $separatorData;
207 }
elseif (!$separatorCandidate || $bestSeparator[0] < $separatorCandidate[0]) {
208 $separatorCandidate = [
212 $this->_values[$bestSeparator[2]],
218 if (!array_key_exists($intervalNumber,
$result) && $separatorCandidate) {
219 $newLastSeparator = $separatorCandidate[2];
220 $newIntervalFirstValue = $separatorCandidate[3];
221 $result[$intervalNumber] = $separatorCandidate[1];
224 if (array_key_exists($intervalNumber,
$result)) {
225 $lastSeparator = $newLastSeparator;
226 $intervalFirstValue = $newIntervalFirstValue;
228 $lastCount +=
$result[$intervalNumber][
'count'];
229 if ($valueIndex != -1 && $lastSeparator > $this->_lastValueLimiter[1]) {
230 $this->_lastValueLimiter = [$valueIndex + $this->_quantileInterval[0], $lastSeparator];
234 if ($this->_lastValueLimiter[0] < $this->_count) {
235 $isEqualValue = $intervalFirstValue == $this->_maxValue ? $intervalFirstValue :
false;
237 'from' => $isEqualValue ? $isEqualValue : $lastSeparator,
238 'to' => $isEqualValue ? $isEqualValue : ($this->_upperLimit ===
null ?
'' :
$this->_upperLimit),
239 'count' => $this->_count - $lastCount,
253 if ($this->_intervalsNumber !==
null) {
278 $intervalValuesCount = $quantileInterval[1] - $quantileInterval[0] + 1;
279 $offset = $quantileInterval[0];
280 if ($this->_lastValueLimiter[0] !==
null) {
281 $offset -= $this->_lastValueLimiter[0];
284 $intervalValuesCount += $offset;
287 $this->_lastValueLimiter[0] + $offset - $this->_quantileInterval[0],
292 $lowerValue = $this->_lastValueLimiter[1];
293 if ($this->_lowerLimit !==
null) {
294 $lowerValue = max($lowerValue, $this->_lowerLimit);
296 if ($intervalValuesCount >= 0) {
299 $interval->
load($intervalValuesCount + 1, $offset, $lowerValue, $this->_upperLimit)
302 $lastValue =
$values[$intervalValuesCount - 1];
303 $bestRoundValue = [];
304 if ($lastValue ==
$values[0]) {
305 if ($quantileNumber == 1 && $offset) {
306 $additionalValues = $interval->
loadPrevious($lastValue, $quantileInterval[0], $this->_lowerLimit);
307 if ($additionalValues) {
308 $quantileInterval[0] -= count($additionalValues);
311 $values[0] + self::MIN_POSSIBLE_VALUE / 10,
319 if (
$values[$valuesCount - 1] > $lastValue) {
320 $additionalValues = [
$values[$valuesCount - 1]];
322 $additionalValues = $interval->
loadNext(
324 $this->_count - $quantileInterval[0] - count(
$values),
328 if ($additionalValues) {
329 $quantileInterval[1] = $quantileInterval[0] + count(
$values) - 1;
330 if (
$values[$valuesCount - 1] <= $lastValue) {
331 $quantileInterval[1] += count($additionalValues);
335 $lastValue + self::MIN_POSSIBLE_VALUE / 10,
344 $values[0] + self::MIN_POSSIBLE_VALUE / 10,
349 $this->_quantileInterval = $quantileInterval;
352 if (empty($bestRoundValue)) {
353 $this->_skippedQuantilesUpperLimits[$quantileNumber] = $quantileInterval[1];
355 return $bestRoundValue;
359 if (
$values[$valuesCount - 1] > $lastValue) {
360 $this->_lastValueLimiter = [$quantileInterval[0] + $valuesCount - 1,
$values[$valuesCount - 1]];
363 ksort($bestRoundValue, SORT_NUMERIC);
364 foreach ($bestRoundValue as
$index => &$bestRoundValueValues) {
365 if (empty($bestRoundValueValues)) {
366 unset($bestRoundValue[
$index]);
368 sort($bestRoundValueValues);
372 return array_reverse($bestRoundValue);
389 min(floor($quantile - $deflectionLimit), floor($quantile)),
390 max(ceil($quantile + $deflectionLimit - 1), ceil($quantile)),
393 $sqrtParam = $this->_count * $quantileNumber * ($this->
getIntervalsNumber() - $quantileNumber);
394 $deflection = self::STANDARD_NORMAL_DISTRIBUTION * sqrt($sqrtParam) / $this->
getIntervalsNumber();
395 $left = max(floor($quantile - $deflection - 1), $limits[0], 0);
396 if (array_key_exists($quantileNumber - 1, $this->_skippedQuantilesUpperLimits)
397 && $left > $this->_skippedQuantilesUpperLimits[$quantileNumber - 1]
399 $left = $this->_skippedQuantilesUpperLimits[$quantileNumber - 1];
401 $right = min(ceil($quantile + $deflection), $limits[1], $this->_count - 1);
403 return [$left, $right];
432 protected function _findRoundValue($lowerValue, $upperValue, $returnEmpty =
true, $roundingFactor =
null)
434 $lowerValue = round($lowerValue, 3);
435 $upperValue = round($upperValue, 3);
437 if ($roundingFactor !==
null) {
439 if ($lowerValue >= $upperValue) {
440 if ($lowerValue > $upperValue || $returnEmpty) {
445 $lowerDivision = ceil(round($lowerValue / $roundingFactor, self::TEN_POWER_ROUNDING_FACTOR + 3));
446 $upperDivision = floor(round($upperValue / $roundingFactor, self::TEN_POWER_ROUNDING_FACTOR + 3));
449 if ($upperDivision <= 0 || $upperDivision - $lowerDivision > 10) {
453 for (
$i = $lowerDivision;
$i <= $upperDivision; ++
$i) {
454 $result[] = round(
$i * $roundingFactor, 2);
461 $tenPower = pow(10, self::TEN_POWER_ROUNDING_FACTOR);
462 $roundingFactorCoefficients = [10, 5, 2];
463 while ($tenPower >= self::MIN_POSSIBLE_VALUE) {
464 if ($tenPower == self::MIN_POSSIBLE_VALUE) {
465 $roundingFactorCoefficients[] = 1;
467 foreach ($roundingFactorCoefficients as $roundingFactorCoefficient) {
468 $roundingFactorCoefficient *= $tenPower;
473 $roundingFactorCoefficient
477 $roundingFactorCoefficient /
478 self::MIN_POSSIBLE_VALUE
498 foreach ($newRoundValues as $roundingFactor => $roundValueValues) {
499 if (array_key_exists($roundingFactor, $oldRoundValues)) {
500 $oldRoundValues[$roundingFactor] = array_unique(
501 array_merge($oldRoundValues[$roundingFactor], $roundValueValues)
504 $oldRoundValues[$roundingFactor] = $roundValueValues;
516 return max(1, $this->
getIntervalsNumber() - count($this->_skippedQuantilesUpperLimits));
531 $valuesCount = count($this->_values);
532 while (
$i < $valuesCount && !empty($separators)) {
538 $separator = array_shift($separators);
541 $quantileNumber * $this->_count -
542 ($this->_quantileInterval[0] +
565 if (empty($this->_values)) {
569 if (!is_array($limits)) {
572 if (!isset($limits[0])) {
575 if (!isset($limits[1])) {
576 $limits[1] = count($this->_values) - 1;
579 if ($limits[0] > $limits[1] || $this->_values[$limits[1]] <
$value) {
583 if ($limits[1] - $limits[0] <= 1) {
584 return $this->_values[$limits[0]] <
$value ? $limits[1] : $limits[0];
587 $separator = floor(($limits[0] + $limits[1]) / 2);
588 if ($this->_values[$separator] <
$value) {
589 $limits[0] = $separator + 1;
591 $limits[1] = $separator;
_getQuantileInterval($quantileNumber)
_findRoundValue($lowerValue, $upperValue, $returnEmpty=true, $roundingFactor=null)
const INTERVAL_DEFLECTION_LIMIT
elseif(isset( $params[ 'redirect_parent']))
load($limit, $offset=null, $lower=null, $upper=null)
const STANDARD_NORMAL_DISTRIBUTION
const MIN_INTERVALS_NUMBER
const MAX_INTERVALS_NUMBER
setLimits($lowerLimit=null, $upperLimit=null)
$_skippedQuantilesUpperLimits
calculateSeparators(IntervalInterface $interval)
_binarySearch($value, $limits=null)
_mergeRoundValues(&$oldRoundValues, &$newRoundValues)
_findValueSeparator($quantileNumber, IntervalInterface $interval)
_findBestSeparator($quantileNumber, $separators)
loadNext($data, $rightIndex, $upper=null)
loadPrevious($data, $index, $lower=null)
setStatistics($min, $max, $standardDeviation, $count)
_getQuantile($quantileNumber)
const TEN_POWER_ROUNDING_FACTOR
_getCalculatedIntervalsNumber()