Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
56 / 56
100.00% covered (success)
100.00%
10 / 10
CRAP
100.00% covered (success)
100.00%
1 / 1
Metrics2D
100.00% covered (success)
100.00%
56 / 56
100.00% covered (success)
100.00%
10 / 10
23
100.00% covered (success)
100.00%
1 / 1
 __construct
n/a
0 / 0
n/a
0 / 0
1
 manhattan
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 euclidean
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 octile
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 chebyshev
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 minkowski
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 canberra
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 brayCurtis
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 angularSeparation
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hamming
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
4
 ulam
100.00% covered (success)
100.00%
26 / 26
100.00% covered (success)
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 */
13declare(strict_types=1);
14
15namespace phpOMS\Math\Topology;
16
17use 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 */
27final 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}