Coverage for src/foapy/ma/_intervals.py: 99%

68 statements  

« prev     ^ index     » next       coverage.py v7.8.0, created at 2025-05-17 20:45 +0000

1import numpy as np 

2from numpy import ma 

3 

4from foapy import binding, mode 

5from foapy.exceptions import InconsistentOrderException, Not1DArrayException 

6 

7 

8def intervals(X, bind, mod): 

9 """ 

10 Finding array of array of intervals of the uniform 

11 sequences in the given input sequence 

12 

13 Parameters 

14 ---------- 

15 X: masked_array 

16 Array to get intervals. 

17 

18 binding: int 

19 binding.start = 1 - Intervals are extracted from left to right. 

20 binding.end = 2 – Intervals are extracted from right to left. 

21 

22 mode: int 

23 mode.lossy = 1 - Both interval from the start of the sequence 

24 to the first element occurrence and interval from the 

25 last element occurrence to the end of the sequence are not taken into account. 

26 

27 mode.normal = 2 - Interval from the start of the sequence to the 

28 first occurrence of the element or interval from the last occurrence 

29 of the element to the end of the sequence is taken into account. 

30 

31 mode.cycle = 3 - Interval from the start of the sequence to the first 

32 element occurrence 

33 and interval from the last element occurrence to the end of the 

34 sequence are summed 

35 into one interval (as if sequence was cyclic). Interval is 

36 placed either in the 

37 beginning of intervals array (in case of binding to the 

38 beginning) or in the end. 

39 

40 mode.redundant = 4 - Both interval from start of the sequence 

41 to the first element 

42 occurrence and the interval from the last element occurrence 

43 to the end of the 

44 sequence are taken into account. Their placement in results 

45 array is determined 

46 by the binding. 

47 

48 Returns 

49 ------- 

50 result: array or Exception. 

51 Exception if not d1 array or wrong mask, array otherwise. 

52 

53 Examples 

54 -------- 

55 

56 ----1---- 

57 >>> import foapy.ma as ma 

58 >>> a = [2, 4, 2, 2, 4] 

59 >>> b = ma.intervals(X, binding.start, mode.lossy) 

60 >>> b 

61 [ 

62 [5], 

63 [1, 4], 

64 [], 

65 [] 

66 ] 

67 

68 ----2---- 

69 >>> import foapy.ma as ma 

70 >>> a = [2, 4, 2, 2, 4] 

71 >>> b = ma.intervals(X, binding.end, mode.lossy) 

72 >>> b 

73 [ 

74 [5], 

75 [1, 4], 

76 [], 

77 [] 

78 ] 

79 

80 ----3---- 

81 >>> import foapy.ma as ma 

82 >>> a = [2, 4, 2, 2, 4] 

83 >>> b = ma.intervals(X, binding.start, mode.normal) 

84 >>> b 

85 [ 

86 [1, 2, 1], 

87 [2, 3] 

88 ] 

89 

90 ----4---- 

91 >>> import foapy.ma as ma 

92 >>> a = [2, 4, 2, 2, 4] 

93 >>> b = ma.intervals(X, binding.end, mode.normal) 

94 >>> b 

95 [ 

96 [2, 1, 2], 

97 [3, 1] 

98 ] 

99 

100 ----5---- 

101 >>> import foapy.ma as ma 

102 >>> a = [2, 4, 2, 2, 4] 

103 >>> b = ma.intervals(X, binding.start, mode.cycle) 

104 >>> b 

105 [ 

106 [2, 2, 1], 

107 [2, 3] 

108 ] 

109 

110 ----6---- 

111 >>> import foapy.ma as ma 

112 >>> a = [2, 4, 2, 2, 4] 

113 >>> b = ma.intervals(X, binding.end, mode.cycle) 

114 >>> b 

115 [ 

116 [2, 1, 2], 

117 [3, 2] 

118 ] 

119 

120 ----7---- 

121 >>> import foapy.ma as ma 

122 >>> a = [2, 4, 2, 2, 4] 

123 >>> b = ma.intervals(X, binding.start, mode.redunant) 

124 >>> b 

125 [ 

126 [1, 2, 1, 2], 

127 [2, 3, 1] 

128 ] 

129 

130 ----8---- 

131 >>> import foapy.ma as ma 

132 >>> a = [2, 4, 2, 2, 4] 

133 >>> b = ma.intervals(X, binding.end, mode.redunant) 

134 >>> b 

135 [ 

136 [1, 2, 1, 2], 

137 [2, 3, 1] 

138 ] 

139 

140 ----9---- 

141 >>> import foapy.ma as ma 

142 >>> a = ['a', 'b', 'c', 'a', 'b', 'c', 'c', 'c', 'b', 'a', 'c', 'b', 'c'] 

143 >>> mask = [0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0] 

144 >>> masked_a = ma.masked_array(a, mask) 

145 >>> b = intervals(X, binding.end, mode.redunant) 

146 >>> b 

147 Exception 

148 

149 ----10---- 

150 >>> import foapy.ma as ma 

151 >>> a = [[2, 2, 2], [2, 2, 2]] 

152 >>> mask = [[0, 0, 0], [0, 0, 0]] 

153 >>> masked_a = ma.masked_array(a, mask) 

154 >>> b = ma.intervals(X, binding.end, mode.redunant) 

155 >>> b 

156 Exception 

157 """ 

158 

159 # Validate binding 

160 if bind not in {binding.start, binding.end}: 

161 raise ValueError( 

162 {"message": "Invalid binding value. Use binding.start or binding.end."} 

163 ) 

164 

165 # Validate mode 

166 if mod not in {mode.lossy, mode.normal, mode.cycle, mode.redundant}: 

167 raise ValueError( 

168 {"message": "Invalid mode value. Use mode.lossy,normal,cycle or redundant."} 

169 ) 

170 # ex.: 

171 # ar = ['a', 'c', 'c', 'e', 'd', 'a'] 

172 

173 power = X.shape[0] 

174 if power == 0: 

175 return np.array([]) 

176 

177 if len(X.shape) != 2: 

178 message = f"Incorrect array form. Expected d2 array, exists {len(X.shape)}" 

179 raise Not1DArrayException({"message": message}) 

180 

181 length = X.shape[1] 

182 

183 mask = ma.getmaskarray(X) 

184 

185 verify_data = X[~mask] 

186 verify_positions = np.transpose(np.argwhere(~mask)) 

187 verify = np.empty(verify_data.shape[0], dtype=bool) 

188 verify[:1] = False 

189 verify[1:] = np.logical_xor( 

190 verify_positions[0, 1:] != verify_positions[0, :-1], 

191 verify_data[1:] != verify_data[:-1], 

192 ) 

193 

194 if not np.all(~verify): 

195 failed_indexes = verify_positions[0, verify] 

196 i = X[failed_indexes[0]] 

197 raise InconsistentOrderException( 

198 {"message": f"Elements {i} have wrong appearance"} 

199 ) 

200 

201 extended_mask = np.empty((power, length + 1), dtype=bool) 

202 if bind == binding.end: 

203 extended_mask[:, :-1] = ~mask[::-1, ::-1] 

204 else: 

205 extended_mask[:, :-1] = ~mask 

206 extended_mask[:, -1] = np.any(extended_mask[:, :-1], axis=1) 

207 

208 positions = np.transpose(np.argwhere(extended_mask)) 

209 

210 border_indexes = np.empty(positions.shape[1] + 1, dtype=bool) 

211 border_indexes[:1] = True 

212 border_indexes[1:-1] = positions[0, 1:] != positions[0, :-1] 

213 border_indexes[-1:] = True 

214 

215 first_indexes = np.argwhere(border_indexes[:-1]).ravel() 

216 last_indexes = np.argwhere(border_indexes[1:]).ravel() 

217 

218 indecies = np.zeros(positions.shape[1], dtype=int) 

219 indecies[1:] = positions[1, 1:] - positions[1, :-1] 

220 delta = indecies[last_indexes] if mod == mode.cycle else 1 

221 indecies[first_indexes] = positions[1][first_indexes] + delta 

222 

223 split_boarders = np.zeros(power * 2, dtype=int) 

224 

225 if mod == mode.lossy: 

226 split_boarders[positions[0][last_indexes[:1]] * 2] = first_indexes[:1] + 1 

227 split_boarders[positions[0][last_indexes[1:]] * 2] = ( 

228 np.fmax(last_indexes[:-1], first_indexes[1:]) + 1 

229 ) 

230 split_boarders[positions[0][last_indexes] * 2 + 1] = last_indexes 

231 elif mod == mode.normal: 

232 split_boarders[positions[0][last_indexes[:1]] * 2] = 0 

233 split_boarders[positions[0][last_indexes[1:]] * 2] = last_indexes[:-1] + 1 

234 split_boarders[positions[0][last_indexes] * 2 + 1] = last_indexes 

235 elif mod == mode.cycle: 

236 split_boarders[positions[0][last_indexes[:1]] * 2] = 0 

237 split_boarders[positions[0][last_indexes[1:]] * 2] = last_indexes[:-1] + 1 

238 split_boarders[positions[0][last_indexes] * 2 + 1] = last_indexes 

239 elif mod == mode.redundant: 239 ↛ 244line 239 didn't jump to line 244 because the condition on line 239 was always true

240 split_boarders[positions[0][last_indexes[:1]] * 2] = 0 

241 split_boarders[positions[0][last_indexes[1:]] * 2] = 0 

242 split_boarders[positions[0][last_indexes] * 2 + 1] = last_indexes + 1 

243 

244 preserve_previous = np.frompyfunc(lambda x, y: x if y == 0 else y, 2, 1) 

245 split_boarders = preserve_previous.accumulate(split_boarders) 

246 if bind == binding.end: 

247 split_boarders[:-1] = np.diff(split_boarders) 

248 split_boarders[-1:] = len(indecies) - split_boarders[-1] 

249 split_boarders = np.cumsum(split_boarders[::-1]) 

250 indecies = indecies[::-1] 

251 

252 indecies = np.array_split(indecies, split_boarders) 

253 return indecies[1:-1:2]