Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
100.00% |
56 / 56 |
|
100.00% |
10 / 10 |
CRAP | |
100.00% |
1 / 1 |
Metrics2D | |
100.00% |
56 / 56 |
|
100.00% |
10 / 10 |
23 | |
100.00% |
1 / 1 |
__construct | n/a |
0 / 0 |
n/a |
0 / 0 |
1 | |||||
manhattan | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
euclidean | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
octile | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
chebyshev | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
minkowski | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
1 | |||
canberra | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
brayCurtis | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
angularSeparation | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
hamming | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
4 | |||
ulam | |
100.00% |
26 / 26 |
|
100.00% |
1 / 1 |
9 |
1 | <?php |
2 | /** |
3 | * Jingga |
4 | * |
5 | * PHP Version 8.1 |
6 | * |
7 | * @package phpOMS\Math\Topology |
8 | * @copyright Dennis Eichhorn |
9 | * @license OMS License 2.0 |
10 | * @version 1.0.0 |
11 | * @link https://jingga.app |
12 | */ |
13 | declare(strict_types=1); |
14 | |
15 | namespace phpOMS\Math\Topology; |
16 | |
17 | use phpOMS\Math\Matrix\Exception\InvalidDimensionException; |
18 | |
19 | /** |
20 | * Metrics. |
21 | * |
22 | * @package phpOMS\Math\Topology |
23 | * @license OMS License 2.0 |
24 | * @link https://jingga.app |
25 | * @since 1.0.0 |
26 | */ |
27 | final class Metrics2D |
28 | { |
29 | /** |
30 | * Constructor |
31 | * |
32 | * @since 1.0.0 |
33 | * @codeCoverageIgnore |
34 | */ |
35 | private function __construct() |
36 | { |
37 | } |
38 | |
39 | /** |
40 | * Manhatten metric. |
41 | * |
42 | * @latex d(p, q) = \sum_{n=1}^N{|p_i - q_i|} |
43 | * |
44 | * @param array<string, int|float> $a 2-D array with x and y coordinate |
45 | * @param array<string, int|float> $b 2-D array with x and y coordinate |
46 | * |
47 | * @return float |
48 | * |
49 | * @since 1.0.0 |
50 | */ |
51 | public static function manhattan(array $a, array $b) : float |
52 | { |
53 | return \abs($a['x'] - $b['x']) + \abs($a['y'] - $b['y']); |
54 | } |
55 | |
56 | /** |
57 | * Euclidean metric. |
58 | * |
59 | * @latex d(p, q) = \sqrt{\sum_{n=1}^N{(p_i - q_i)^2}} |
60 | * |
61 | * @param array<string, int|float> $a 2-D array with x and y coordinate |
62 | * @param array<string, int|float> $b 2-D array with x and y coordinate |
63 | * |
64 | * @return float |
65 | * |
66 | * @since 1.0.0 |
67 | */ |
68 | public static function euclidean(array $a, array $b) : float |
69 | { |
70 | $dx = \abs($a['x'] - $b['x']); |
71 | $dy = \abs($a['y'] - $b['y']); |
72 | |
73 | return \sqrt($dx * $dx + $dy * $dy); |
74 | } |
75 | |
76 | /** |
77 | * Octile metric. |
78 | * |
79 | * @latex d(p, q) = \begin{cases}(\sqrt{2} - 1) \times |p_i - q_i| + |p_{i+1} - q_{i+1}|,& \text{if } |p_i - q_i| < |p_{i+1} - q_{i+1}|\\(\sqrt{2} - 1) \times |p_{i+1} - q_{i+1}| + |p_i - q_i|,&\text{if } |p_i - q_i| \geq |p_{i+1} - q_{i+1}|\end{cases} |
80 | * |
81 | * @param array<string, int|float> $a 2-D array with x and y coordinate |
82 | * @param array<string, int|float> $b 2-D array with x and y coordinate |
83 | * |
84 | * @return float |
85 | * |
86 | * @since 1.0.0 |
87 | */ |
88 | public static function octile(array $a, array $b) : float |
89 | { |
90 | $dx = \abs($a['x'] - $b['x']); |
91 | $dy = \abs($a['y'] - $b['y']); |
92 | |
93 | return $dx < $dy ? (\sqrt(2) - 1) * $dx + $dy : (\sqrt(2) - 1) * $dy + $dx; |
94 | } |
95 | |
96 | /** |
97 | * Chebyshev metric. |
98 | * |
99 | * @latex d(p, q) = \max_i{(|p_i - q_i|)} |
100 | * |
101 | * @param array<string, int|float> $a 2-D array with x and y coordinate |
102 | * @param array<string, int|float> $b 2-D array with x and y coordinate |
103 | * |
104 | * @return float |
105 | * |
106 | * @since 1.0.0 |
107 | */ |
108 | public static function chebyshev(array $a, array $b) : float |
109 | { |
110 | return \max( |
111 | \abs($a['x'] - $b['x']), |
112 | \abs($a['y'] - $b['y']) |
113 | ); |
114 | } |
115 | |
116 | /** |
117 | * Minkowski metric. |
118 | * |
119 | * @latex d(p, q) = \sqrt[\lambda]{\sum_{n=1}^N{|p_i - q_i|^\lambda}} |
120 | * |
121 | * @param array<string, int|float> $a 2-D array with x and y coordinate |
122 | * @param array<string, int|float> $b 2-D array with x and y coordinate |
123 | * @param int $lambda Lambda |
124 | * |
125 | * @return float |
126 | * |
127 | * @since 1.0.0 |
128 | */ |
129 | public static function minkowski(array $a, array $b, int $lambda) : float |
130 | { |
131 | return \pow( |
132 | \pow(\abs($a['x'] - $b['x']), $lambda) |
133 | + \pow(\abs($a['y'] - $b['y']), $lambda), |
134 | 1 / $lambda |
135 | ); |
136 | } |
137 | |
138 | /** |
139 | * Canberra metric. |
140 | * |
141 | * @latex d(p, q) = \sum_{n=1}^N{\frac{|p_i - q_i|}{|p_i| + |q_i|} |
142 | * |
143 | * @param array<string, int|float> $a 2-D array with x and y coordinate |
144 | * @param array<string, int|float> $b 2-D array with x and y coordinate |
145 | * |
146 | * @return float |
147 | * |
148 | * @since 1.0.0 |
149 | */ |
150 | public static function canberra(array $a, array $b) : float |
151 | { |
152 | return \abs($a['x'] - $b['x']) / (\abs($a['x']) + \abs($b['x'])) |
153 | + \abs($a['y'] - $b['y']) / (\abs($a['y']) + \abs($b['y'])); |
154 | } |
155 | |
156 | /** |
157 | * Bray Curtis metric. |
158 | * |
159 | * @latex d(p, q) = \frac{\sum_{n=1}^N{|p_i - q_i|}}{\sum_{n=1}^N{(p_i + q_i)}} |
160 | * |
161 | * @param array<string, int|float> $a 2-D array with x and y coordinate |
162 | * @param array<string, int|float> $b 2-D array with x and y coordinate |
163 | * |
164 | * @return float |
165 | * |
166 | * @since 1.0.0 |
167 | */ |
168 | public static function brayCurtis(array $a, array $b) : float |
169 | { |
170 | return (\abs($a['x'] - $b['x']) |
171 | + \abs($a['y'] - $b['y'])) |
172 | / (($a['x'] + $b['x']) |
173 | + ($a['y'] + $b['y'])); |
174 | } |
175 | |
176 | /** |
177 | * Angular separation metric. |
178 | * |
179 | * @latex d(p, q) = \frac{\sum_{n=1}^N{p_i * q_i}}{\left(\sum_{n=1}^N{p_i^2} * \sum_{n=1}^N{q_i^2}\right)^\frac{1}{2}} |
180 | * |
181 | * @param array<string, int|float> $a 2-D array with x and y coordinate |
182 | * @param array<string, int|float> $b 2-D array with x and y coordinate |
183 | * |
184 | * @return float |
185 | * |
186 | * @since 1.0.0 |
187 | */ |
188 | public static function angularSeparation(array $a, array $b) : float |
189 | { |
190 | return ($a['x'] * $b['x'] + $a['y'] * $b['y']) / \pow(($a['x'] ** 2 + $a['y'] ** 2) * ($b['x'] ** 2 + $b['y'] ** 2), 1 / 2); |
191 | } |
192 | |
193 | /** |
194 | * Hamming metric. |
195 | * |
196 | * @latex d(p, q) = \sum_{n=1}^N{|p_i - q_i|} |
197 | * |
198 | * @param array<int, int|float> $a 2-D array with x and y coordinate |
199 | * @param array<int, int|float> $b 2-D array with x and y coordinate |
200 | * |
201 | * @return int |
202 | * |
203 | * @throws InvalidDimensionException |
204 | * |
205 | * @since 1.0.0 |
206 | */ |
207 | public static function hamming(array $a, array $b) : int |
208 | { |
209 | if (($size = \count($a)) !== \count($b)) { |
210 | throw new InvalidDimensionException(\count($a) . 'x' . \count($b)); |
211 | } |
212 | |
213 | $dist = 0; |
214 | for ($i = 0; $i < $size; ++$i) { |
215 | if ($a[$i] !== $b[$i]) { |
216 | ++$dist; |
217 | } |
218 | } |
219 | |
220 | return $dist; |
221 | } |
222 | |
223 | /** |
224 | * Ulams metric. |
225 | * |
226 | * Calculate the minimum amount of changes to make two arrays the same. |
227 | * |
228 | * In order to use this with objects the objects would have to implement some kind of value representation for comparison. |
229 | * |
230 | * @param array<int, int|float> $a Array with elements |
231 | * @param array<int, int|float> $b Array with same elements but different order |
232 | * |
233 | * @return int |
234 | * |
235 | * @throws InvalidDimensionException |
236 | * |
237 | * @since 1.0.0 |
238 | */ |
239 | public static function ulam(array $a, array $b) : int |
240 | { |
241 | if (($size = \count($a)) !== \count($b)) { |
242 | throw new InvalidDimensionException(\count($a) . 'x' . \count($b)); |
243 | } |
244 | |
245 | $mp = []; |
246 | for ($i = 0; $i < $size; ++$i) { |
247 | $mp[$b[$i]] = $i; |
248 | } |
249 | |
250 | for ($i = 0; $i < $size; ++$i) { |
251 | $b[$i] = $mp[$a[$i]]; |
252 | } |
253 | |
254 | $bPos = []; |
255 | for ($i = 0; $i < $size; ++$i) { |
256 | $bPos[$i] = [$b[$i], $i]; |
257 | } |
258 | |
259 | \usort($bPos, function ($e1, $e2) { |
260 | return $e1[0] <=> $e2[0]; |
261 | }); |
262 | |
263 | $vis = \array_fill(0, $size, false); |
264 | $ans = 0; |
265 | |
266 | for ($i = 0; $i < $size; ++$i) { |
267 | if ($vis[$i] || $bPos[$i][1] === $i) { |
268 | continue; |
269 | } |
270 | |
271 | $cycleSize = 0; |
272 | $j = $i; |
273 | |
274 | while (!$vis[$j]) { |
275 | $vis[$j] = true; |
276 | $j = $bPos[$j][1]; |
277 | |
278 | ++$cycleSize; |
279 | } |
280 | |
281 | $ans += $cycleSize - 1; |
282 | } |
283 | |
284 | return $ans; |
285 | } |
286 | } |