Coverage for src/foapy/core/_order.py: 100%
24 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-05-17 20:45 +0000
« prev ^ index » next coverage.py v7.8.0, created at 2025-05-17 20:45 +0000
1import numpy as np
2from numpy import ndarray
4from foapy.exceptions import Not1DArrayException
7def order(X, return_alphabet: bool = False) -> ndarray:
8 """
10 Decompose an array into an order and an alphabet.
12 Alphabet is a list of all unique values from the input array in order of their first appearance.
13 Order is an array of indices that maps each element in the input array to its position
14 in the alphabet.
16 | Input array X | Order | Alphabet | Note |
17 |---------------|-------------|-----------|---------------------------------------------------|
18 | [ x y x z ] | [ 0 1 0 2 ] | [ x y z ] | Example decomposition into order and alphabet |
19 | [ y x y z ] | [ 0 1 0 2 ] | [ y x z ] | Same order as above, different alphabet |
20 | [ y y x z ] | [ 0 0 1 2 ] | [ y x z ] | Same alphabet as above, different order |
21 | [ ] | [ ] | [ ] | Empty array |
23 Parameters
24 ----------
25 X : np.array_like
26 Array to decompose into an order and an alphabet. Must be a 1-dimensional array.
28 return_alphabet : bool, optional
29 If True also return array's alphabet
31 Returns
32 -------
33 order : ndarray
34 Order of X
36 alphabet : ndarray
37 Alphabet of X. Only provided if `return_alphabet` is True.
39 Raises
40 -------
41 Not1DArrayException
42 When X parameter is not d1 array
44 Examples
45 --------
47 Get an order of a characters sequence.
49 ``` py linenums="1"
50 import foapy
51 source = ['a', 'b', 'a', 'c', 'd']
52 order = foapy.order(source)
53 print(order)
54 # [0, 1, 0, 2, 3]
55 ```
57 Reconstruct original sequence from the order and the alphabet.
59 ``` py linenums="1"
60 import foapy
61 source = ['a', 'c', 'c', 'e', 'd', 'a']
62 order, alphabet = foapy.order(source, True)
63 print(order, alphabet)
64 # [0, 1, 1, 2, 3, 0] ['a', 'c', 'e', 'd']
65 restored = alphabet[order]
66 print(restored)
67 # ['a', 'c', 'c', 'e', 'd', 'a']
68 ```
70 An order of an empty sequence is empty array.
72 ``` py linenums="1"
73 import foapy
74 source = []
75 order = foapy.order(source)
76 print(order)
77 # []
78 ```
80 Getting an order of an array with more than 1 dimension is not allowed
82 ``` py linenums="1"
83 import foapy
84 source = [[[1], [3]], [[6], [9]], [[6], [3]]]
85 order = foapy.order(source)
86 # Not1DArrayException: {'message': 'Incorrect array form. Expected d1 array, exists 3'}
87 ```
88 """ # noqa: E501
90 data = np.asanyarray(X)
91 if data.ndim > 1: # Checking for d1 array
92 raise Not1DArrayException(
93 {"message": f"Incorrect array form. Expected d1 array, exists {data.ndim}"}
94 )
96 perm = data.argsort(kind="mergesort")
98 unique_mask = np.empty(data.shape, dtype=bool)
99 unique_mask[:1] = True
100 unique_mask[1:] = data[perm[1:]] != data[perm[:-1]]
102 result_mask = np.zeros_like(unique_mask)
103 result_mask[:1] = True
104 result_mask[perm[unique_mask]] = True
106 power = np.count_nonzero(unique_mask)
108 inverse_perm = np.empty(data.shape, dtype=np.intp)
109 inverse_perm[perm] = np.arange(data.shape[0])
111 result = np.cumsum(unique_mask) - 1
112 inverse_alphabet_perm = np.empty(power, dtype=np.intp)
113 inverse_alphabet_perm[result[inverse_perm][result_mask]] = np.arange(power)
115 result = inverse_alphabet_perm[result][inverse_perm]
117 if return_alphabet:
118 return result, data[result_mask]
119 return result