LargestRemainder   A
last analyzed

Complexity

Total Complexity 14

Size/Duplication

Total Lines 128
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 14
eloc 47
c 2
b 0
f 0
dl 0
loc 128
ccs 44
cts 44
cp 1
rs 10

6 Methods

Rating   Name   Duplication   Size   Complexity  
A setPrecision() 0 3 1
A round() 0 8 1
A __construct() 0 3 1
B uround() 0 47 8
A getNumber() 0 7 2
A setNumber() 0 3 1
1
<?php
2
3
namespace Aeq\LargestRemainder\Math;
4
5
use Aeq\LargestRemainder\Exception\AlreadyNormalizedException;
6
use Aeq\LargestRemainder\Exception\NotANumberException;
7
use Aeq\LargestRemainder\Exception\NotYetNormalizedException;
8
use Aeq\LargestRemainder\Math\Number as LargestRemainderNumber;
9
10
class LargestRemainder
11
{
12
    /**
13
     * @var array
14
     */
15
    private $numbers = [];
16
17
    /**
18
     * @var int
19
     */
20
    private $precision = 0;
21
22
    /**
23
     * @param array $numbers
24
     */
25 11
    public function __construct(array $numbers)
26
    {
27 11
        $this->numbers = $numbers;
28 11
    }
29
30
    /**
31
     * @param int $precision
32
     */
33 6
    public function setPrecision(int $precision): void
34
    {
35 6
        $this->precision = $precision;
36 6
    }
37
38
    /**
39
     * @return array
40
     * @throws AlreadyNormalizedException
41
     * @throws NotANumberException
42
     * @throws NotYetNormalizedException
43
     */
44 10
    public function round(): array
45
    {
46 10
        return $this->uround(
47
            function ($item) {
48 10
                return $item;
49 10
            },
50
            function (&$item, $value) {
51 9
                $item = $value;
52 10
            }
53
        );
54
    }
55
56
    /**
57
     * @param callable $get
58
     * @param callable $set
59
     * @return array
60
     * @throws NotANumberException
61
     * @throws AlreadyNormalizedException
62
     * @throws NotYetNormalizedException
63
     */
64 11
    public function uround(callable $get, callable $set): array
65
    {
66 11
        $originalOrder = array_keys($this->numbers);
67
68
        $sum = 0;
69 11
        $floorSum = 0;
70 11
71
        foreach ($this->numbers as $raw) {
72 10
            $number = $this->getNumber($get, $raw);
73
            $sum += $number->value();
74
            $floorSum += $number->floor()->value();
75 10
        }
76 10
77 10
        $diff = round($sum) - round($floorSum);
78 10
79
        uasort($this->numbers, function ($a, $b) use ($get) {
80 10
            $aNumber = $this->getNumber($get, $a);
81 10
            $bNumber = $this->getNumber($get, $b);
82 10
            $aDiff = $aNumber->value() - $aNumber->floor()->value();
83 10
            $bDiff = $bNumber->value() - $bNumber->floor()->value();
84 5
            if($aDiff === $bDiff) {
85 5
                return 0;
86 5
            }
87
            if ($aDiff > $bDiff) {
88 9
                return -1;
89 4
            }
90 4
            return 1;
91 4
        });
92
93 8
        $index = 0;
94 8
        foreach ($this->numbers as &$item) {
95 8
            $number = $this->getNumber($get, $item);
96
            if ($index < $diff) {
97
                $this->setNumber($set, $item, $number->add(1)->floor());
98 10
                $index++;
99
                continue;
100
            }
101
            if ($diff < 0 && $index < $diff * (-1)) {
102
                $this->setNumber($set, $item, $number->sub(1)->floor());
103
                $index++;
104
                continue;
105
            }
106
            $this->setNumber($set, $item, $number->floor());
107
            $index++;
108 11
        }
109
110 11
        return array_replace(array_flip($originalOrder), $this->numbers);
111 11
    }
112 1
113
    /**
114 11
     * @param callable $get
115
     * @param $val
116
     * @return LargestRemainderNumber
117
     * @throws NotANumberException
118
     * @throws AlreadyNormalizedException
119
     */
120
    private function getNumber(callable $get, $val): LargestRemainderNumber
121
    {
122
        $resolved = call_user_func_array($get, [$val]);
123 10
        if (false === is_numeric($resolved)) {
124
            throw new NotANumberException($val, 1538927918);
125 10
        }
126 10
        return (new Number($resolved, $this->precision))->normalize();
127
    }
128
129
    /**
130
     * @param callable $set
131
     * @param $item
132
     * @param LargestRemainderNumber $number
133
     * @throws NotYetNormalizedException
134
     */
135
    private function setNumber(callable $set, &$item, LargestRemainderNumber $number): void
136
    {
137
        call_user_func_array($set, [&$item, $number->denormalize()->value()]);
138
    }
139
}
140