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

1import numpy as np 

2from numpy import ndarray 

3 

4from foapy.exceptions import Not1DArrayException 

5 

6 

7def order(X, return_alphabet: bool = False) -> ndarray: 

8 """ 

9 

10 Decompose an array into an order and an alphabet. 

11 

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. 

15 

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 | 

22 

23 Parameters 

24 ---------- 

25 X : np.array_like 

26 Array to decompose into an order and an alphabet. Must be a 1-dimensional array. 

27 

28 return_alphabet : bool, optional 

29 If True also return array's alphabet 

30 

31 Returns 

32 ------- 

33 order : ndarray 

34 Order of X 

35 

36 alphabet : ndarray 

37 Alphabet of X. Only provided if `return_alphabet` is True. 

38 

39 Raises 

40 ------- 

41 Not1DArrayException 

42 When X parameter is not d1 array 

43 

44 Examples 

45 -------- 

46 

47 Get an order of a characters sequence. 

48 

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 ``` 

56 

57 Reconstruct original sequence from the order and the alphabet. 

58 

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 ``` 

69 

70 An order of an empty sequence is empty array. 

71 

72 ``` py linenums="1" 

73 import foapy 

74 source = [] 

75 order = foapy.order(source) 

76 print(order) 

77 # [] 

78 ``` 

79 

80 Getting an order of an array with more than 1 dimension is not allowed 

81 

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 

89 

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 ) 

95 

96 perm = data.argsort(kind="mergesort") 

97 

98 unique_mask = np.empty(data.shape, dtype=bool) 

99 unique_mask[:1] = True 

100 unique_mask[1:] = data[perm[1:]] != data[perm[:-1]] 

101 

102 result_mask = np.zeros_like(unique_mask) 

103 result_mask[:1] = True 

104 result_mask[perm[unique_mask]] = True 

105 

106 power = np.count_nonzero(unique_mask) 

107 

108 inverse_perm = np.empty(data.shape, dtype=np.intp) 

109 inverse_perm[perm] = np.arange(data.shape[0]) 

110 

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) 

114 

115 result = inverse_alphabet_perm[result][inverse_perm] 

116 

117 if return_alphabet: 

118 return result, data[result_mask] 

119 return result