Coverage for moptipyapps / order1d / distances.py: 32%
19 statements
« prev ^ index » next coverage.py v7.13.0, created at 2025-12-11 04:40 +0000
« prev ^ index » next coverage.py v7.13.0, created at 2025-12-11 04:40 +0000
1"""Some examples for distance metrics."""
3from typing import Final
5import numba # type: ignore
6import numpy as np
7from moptipy.utils.nputils import DEFAULT_BOOL
10@numba.njit(cache=True, inline="always", fastmath=True, boundscheck=False)
11def swap_distance(p1: np.ndarray, p2: np.ndarray) -> int:
12 """
13 Compute the swap distance between two permutations `p1` and `p1`.
15 This is the minimum number of swaps required to translate `p1` to `p2` and
16 vice versa. This function is symmatric.
18 An upper bound for the number of maximum number of swaps that could be
19 required is the length of the permutation. This upper bound can be derived
20 from Selection Sort. Imagine that I want to translate the array `p1` to
21 `p2`. I go through `p1` from beginning to end. If, at index `i`, I find
22 the right element (`p1[i] == p2[i]`), then I do nothing. If not, then the
23 right element must come at some index `j>i` (because all elements before I
24 already have fixed). So I swap `p1[i]` with `p1[j]`. Now `p1[i] == p2[i]`
25 and I increment `i`. Once I arrive at the end of `p1`, it must hold that
26 `all(p1[i] == p2[i])`. At the same time, I have performed at most one swap
27 at each index during the iteration. Hence, I can never need more swaps
28 than the arrays are long.
30 :param p1: the first permutation
31 :param p2: the second permutation
32 :return: the swap distance, always between `0` and `len(p1)`
34 >>> swap_distance(np.array([0, 1, 2, 3]), np.array([3, 1, 2, 0]))
35 1
36 >>> swap_distance(np.array([0, 1, 2]), np.array([0, 1, 2]))
37 0
38 >>> swap_distance(np.array([1, 0, 2]), np.array([0, 1, 2]))
39 1
40 >>> swap_distance(np.array([0, 1, 2]), np.array([1, 0, 2]))
41 1
42 >>> swap_distance(np.array([0, 1, 2]), np.array([2, 0, 1]))
43 2
44 >>> swap_distance(np.array([2, 0, 1]), np.array([0, 1, 2]))
45 2
46 >>> swap_distance(np.arange(10), np.array([4, 8, 1, 5, 9, 3, 6, 0, 7, 2]))
47 7
48 >>> swap_distance(np.array([4, 8, 1, 5, 9, 3, 6, 0, 7, 2]), np.arange(10))
49 7
50 """
51 n: Final[int] = len(p1)
52 x: np.ndarray = p2[np.argsort(p1)]
53 unchecked: np.ndarray = np.ones(n, DEFAULT_BOOL)
54 result: int = 0
56 for i in range(n):
57 if unchecked[i]:
58 result += 1
59 unchecked[i] = False
60 j = x[i]
61 while j != i:
62 unchecked[j] = False
63 j = x[j]
65 return n - result