11"""Methods for list of ranges [inclusive, inclusive]"""
22# pylint: disable=too-many-branches
33import bisect
4- from copy import deepcopy
5- from typing import Iterator , Optional , Union
4+ from itertools import chain
5+ from typing import Optional , Union
66
77from kytos .core .exceptions import KytosInvalidTagRanges
88
@@ -94,9 +94,8 @@ def get_validated_tags(
9494def range_intersection (
9595 ranges_a : list [list [int ]],
9696 ranges_b : list [list [int ]],
97- reverse : bool = False ,
98- ) -> Iterator [list [int ]]:
99- """Returns an iterator of an intersection between
97+ ) -> list [list [int ]]:
98+ """Returns a list of ranges of an intersection between
10099 two validated list of ranges.
101100
102101 Necessities:
@@ -105,37 +104,50 @@ def range_intersection(
105104 Use get_tag_ranges() for list[list[int]] or
106105 get_validated_tags() for also list[int]
107106 """
108- a_i , b_i = 0 , 0
109- index_diff = 1
110- if reverse :
111- a_i = len (ranges_a ) - 1
112- b_i = len (ranges_b ) - 1
113- index_diff = - 1
114- while 0 <= a_i < len (ranges_a ) and 0 <= b_i < len (ranges_b ):
115- fst_a , snd_a = ranges_a [a_i ]
116- fst_b , snd_b = ranges_b [b_i ]
117- # Moving forward with non-intersection
118- if snd_a < fst_b :
119- if not reverse :
120- a_i += index_diff
121- else :
122- b_i += index_diff
123- elif snd_b < fst_a :
124- if not reverse :
125- b_i += index_diff
126- else :
127- a_i += index_diff
128- else :
129- # Intersection
130- intersection_start = max (fst_a , fst_b )
131- intersection_end = min (snd_a , snd_b )
132- yield [intersection_start , intersection_end ]
133- move_from_a = snd_a < snd_b if not reverse else fst_a > fst_b
134- if move_from_a :
135- a_i += index_diff
136- else :
137- b_i += index_diff
107+ if not ranges_a :
108+ return []
109+ if not ranges_b :
110+ return []
111+
112+ a_bounds = (ranges_a [0 ][0 ], ranges_b [- 1 ][1 ])
113+ b_bounds = (ranges_b [0 ][0 ], ranges_b [- 1 ][1 ])
114+
115+ true_bounds = max (a_bounds [0 ], b_bounds [0 ]), min (a_bounds [1 ], b_bounds [1 ])
116+
117+ # Could optimize the process to only require at most 2 cuts,
118+ # rather than the 4 currently done.
119+ _ , bounded_a , _ = partition_by_relevant_bounds (
120+ ranges_a , * true_bounds
121+ )
122+
123+ _ , bounded_b , _ = partition_by_relevant_bounds (
124+ ranges_b , * true_bounds
125+ )
126+
127+ ordered_ranges = sorted (chain (
128+ bounded_a ,
129+ bounded_b
130+ ))
131+
132+ intersections = list [list [int ]]()
138133
134+ top = ordered_ranges [0 ]
135+
136+ for tag_range in ordered_ranges [1 :]:
137+ match top , tag_range :
138+ case [a_start , a_end ], [b_start , b_end ] if (
139+ b_start <= a_end and a_end <= b_end
140+ ):
141+ top = [a_start , b_end ]
142+ intersections .append ([b_start , a_end ])
143+ case [a_start , a_end ], [b_start , b_end ] if (
144+ b_end < a_end
145+ ):
146+ intersections .append ([b_start , b_end ])
147+ case _, _:
148+ top = tag_range
149+
150+ return intersections
139151
140152def range_difference (
141153 ranges_a : list [Optional [list [int ]]],
@@ -152,43 +164,79 @@ def range_difference(
152164 get_validated_tags() for also list[int]
153165 """
154166 if not ranges_a :
155- return []
167+ return ranges_a
156168 if not ranges_b :
157- return deepcopy (ranges_a )
158- result = []
159- a_i , b_i = 0 , 0
160- update = True
161- while a_i < len (ranges_a ) and b_i < len (ranges_b ):
162- if update :
163- start_a , end_a = ranges_a [a_i ]
164- else :
165- update = True
166- start_b , end_b = ranges_b [b_i ]
167- # Moving forward with non-intersection
168- if end_a < start_b :
169- result .append ([start_a , end_a ])
170- a_i += 1
171- elif end_b < start_a :
172- b_i += 1
173- else :
174- # Intersection
175- if start_a < start_b :
176- result .append ([start_a , start_b - 1 ])
177- if end_a > end_b :
178- start_a = end_b + 1
179- update = False
180- b_i += 1
181- else :
182- a_i += 1
183- # Append last intersection and the rest of ranges_a
184- while a_i < len (ranges_a ):
185- if update :
186- start_a , end_a = ranges_a [a_i ]
187- else :
188- update = True
189- result .append ([start_a , end_a ])
190- a_i += 1
191- return result
169+ return ranges_a
170+
171+ a_bounds = (ranges_a [0 ][0 ], ranges_b [- 1 ][1 ])
172+ b_bounds = (ranges_b [0 ][0 ], ranges_b [- 1 ][1 ])
173+
174+ true_bounds = max (a_bounds [0 ], b_bounds [0 ]), min (a_bounds [1 ], b_bounds [1 ])
175+
176+ unaffected_left , bounded_a , unaffected_right = partition_by_relevant_bounds (
177+ ranges_a , * true_bounds
178+ )
179+
180+ _ , bounded_b , _ = partition_by_relevant_bounds (
181+ ranges_b , * true_bounds
182+ )
183+
184+ ordered_ranges = sorted (chain (
185+ [[* range_a , False ] for range_a in bounded_a ],
186+ [[* range_b , True ] for range_b in bounded_b ]
187+ ))
188+
189+ merged_ranges = list [list [int ]]()
190+
191+ top = ordered_ranges [0 ]
192+
193+ for tag_range in ordered_ranges [1 :]:
194+ # implied a_start <= b_start
195+ match top , tag_range :
196+ case [a_start , a_end , subtract_a ], [b_start , b_end , subtract_b ] if (
197+ subtract_a and subtract_b
198+ ):
199+ top = tag_range
200+ # subtract_a implies not substract_b
201+ # subtract_b implies not subtract_a
202+ case [a_start , a_end , subtract_a ], [b_start , b_end , subtract_b ] if (
203+ subtract_a and b_end <= a_end
204+ ):
205+ pass
206+ case [a_start , a_end , subtract_a ], [b_start , b_end , subtract_b ] if (
207+ subtract_a and b_start <= a_end
208+ ):
209+ top = [a_end + 1 , b_end , False ]
210+ case [a_start , a_end , subtract_a ], [b_start , b_end , subtract_b ] if (
211+ subtract_b and a_start == b_start and a_end <= b_end
212+ ):
213+ top = tag_range
214+ case [a_start , a_end , subtract_a ], [b_start , b_end , subtract_b ] if (
215+ subtract_b and a_start == b_start
216+ ):
217+ top = [b_end + 1 , a_end , False ]
218+ case [a_start , a_end , subtract_a ], [b_start , b_end , subtract_b ] if (
219+ subtract_b and b_end < a_end
220+ ):
221+ merged_ranges .append ([a_start , b_start - 1 ])
222+ top = [b_end + 1 , a_end , False ]
223+ case [a_start , a_end , subtract_a ], [b_start , b_end , subtract_b ] if (
224+ subtract_b and b_start <= a_end
225+ ):
226+ merged_ranges .append ([a_start , b_start - 1 ])
227+ top = tag_range
228+ case [a_start , a_end , subtract_a ], [b_start , b_end , subtract_b ] if (
229+ not subtract_a
230+ ):
231+ merged_ranges .append ([top [0 ], top [1 ]])
232+ top = tag_range
233+ case _, _:
234+ top = tag_range
235+
236+ if not top [2 ]:
237+ merged_ranges .append ([top [0 ], top [1 ]])
238+
239+ return [* unaffected_left , * merged_ranges , * unaffected_right ]
192240
193241
194242def range_addition (
@@ -205,57 +253,66 @@ def range_addition(
205253 Use get_tag_ranges() for list[list[int]] or
206254 get_validated_tags() for also list[int]
207255 """
208- if not ranges_b :
209- return deepcopy (ranges_a ), []
210256 if not ranges_a :
211- return deepcopy (ranges_b ), []
212- result = []
213- conflict = []
214- a_i = b_i = 0
215- len_a = len (ranges_a )
216- len_b = len (ranges_b )
217- while a_i < len_a or b_i < len_b :
218- if (a_i < len_a and
219- (b_i >= len_b or ranges_a [a_i ][1 ] < ranges_b [b_i ][0 ] - 1 )):
220- result .append (ranges_a [a_i ])
221- a_i += 1
222- elif (b_i < len_b and
223- (a_i >= len_a or ranges_b [b_i ][1 ] < ranges_a [a_i ][0 ] - 1 )):
224- result .append (ranges_b [b_i ])
225- b_i += 1
226- # Intersection and continuos ranges
227- else :
228- fst = max (ranges_a [a_i ][0 ], ranges_b [b_i ][0 ])
229- snd = min (ranges_a [a_i ][1 ], ranges_b [b_i ][1 ])
230- if fst <= snd :
231- conflict .append ([fst , snd ])
232- new_range = [
233- min (ranges_a [a_i ][0 ], ranges_b [b_i ][0 ]),
234- max (ranges_a [a_i ][1 ], ranges_b [b_i ][1 ])
235- ]
236- a_i += 1
237- b_i += 1
238- while a_i < len_a or b_i < len_b :
239- if a_i < len_a and (ranges_a [a_i ][0 ] <= new_range [1 ] + 1 ):
240- if ranges_a [a_i ][0 ] <= new_range [1 ]:
241- conflict .append ([
242- max (ranges_a [a_i ][0 ], new_range [0 ]),
243- min (ranges_a [a_i ][1 ], new_range [1 ])
244- ])
245- new_range [1 ] = max (ranges_a [a_i ][1 ], new_range [1 ])
246- a_i += 1
247- elif b_i < len_b and (ranges_b [b_i ][0 ] <= new_range [1 ] + 1 ):
248- if ranges_b [b_i ][0 ] <= new_range [1 ]:
249- conflict .append ([
250- max (ranges_b [b_i ][0 ], new_range [0 ]),
251- min (ranges_b [b_i ][1 ], new_range [1 ])
252- ])
253- new_range [1 ] = max (ranges_b [b_i ][1 ], new_range [1 ])
254- b_i += 1
255- else :
256- break
257- result .append (new_range )
258- return result , conflict
257+ return ranges_b , []
258+ if not ranges_b :
259+ return ranges_a , []
260+
261+ a_bounds = (ranges_a [0 ][0 ], ranges_b [- 1 ][1 ])
262+ b_bounds = (ranges_b [0 ][0 ], ranges_b [- 1 ][1 ])
263+
264+ # Slightly adjusted to incorporate merges along boundaries e.g. [[1,2]] + [[3,4]]
265+ true_bounds = max (a_bounds [0 ], b_bounds [0 ]) - 1 , min (a_bounds [1 ], b_bounds [1 ]) + 1
266+
267+ unaffected_left_a , bounded_a , unaffected_right_a = partition_by_relevant_bounds (
268+ ranges_a , * true_bounds
269+ )
270+
271+ unaffected_left_b , bounded_b , unaffected_right_b = partition_by_relevant_bounds (
272+ ranges_b , * true_bounds
273+ )
274+
275+ ordered_ranges = sorted (chain (
276+ bounded_a ,
277+ bounded_b
278+ ))
279+
280+ merged_ranges = list [list [int ]]()
281+ intersections = list [list [int ]]()
282+
283+ top = ordered_ranges [0 ]
284+
285+ for tag_range in ordered_ranges [1 :]:
286+ match top , tag_range :
287+ case [a_start , a_end ], [b_start , b_end ] if (
288+ a_end + 1 == b_start
289+ ):
290+ top = [a_start , b_end ]
291+ case [a_start , a_end ], [b_start , b_end ] if (
292+ b_start <= a_end and a_end <= b_end
293+ ):
294+ top = [a_start , b_end ]
295+ intersections .append ([b_start , a_end ])
296+ case [a_start , a_end ], [b_start , b_end ] if (
297+ b_end < a_end
298+ ):
299+ intersections .append ([b_start , b_end ])
300+ case _, _:
301+ merged_ranges .append (top )
302+ top = tag_range
303+
304+ merged_ranges .append (top )
305+
306+ return (
307+ [
308+ * unaffected_left_a ,
309+ * unaffected_left_b ,
310+ * merged_ranges ,
311+ * unaffected_right_a ,
312+ * unaffected_right_b ,
313+ ],
314+ intersections
315+ )
259316
260317
261318def find_index_remove (
@@ -287,3 +344,51 @@ def find_index_add(
287344 tags [1 ] < available_tags [index ][0 ]):
288345 return index
289346 return None
347+
348+
349+ def partition_by_leftmost (
350+ tag_ranges : list [list [int ]],
351+ bound : int
352+ ):
353+ bound_range = [bound , bound ]
354+ index = bisect .bisect_left (tag_ranges , bound_range )
355+ if index == 0 :
356+ return [], tag_ranges
357+ left_tags = tag_ranges [:index ]
358+ right_tags = tag_ranges [index :]
359+ match left_tags , right_tags :
360+ case [* _, [_, a_end ]], [* _] if (
361+ bound <= a_end
362+ ):
363+ return tag_ranges [:index - 1 ], tag_ranges [index - 1 :]
364+ case [* _], [* _]:
365+ return left_tags , right_tags
366+
367+
368+ def partition_by_rightmost (
369+ tag_ranges : list [list [int ]],
370+ bound : int
371+ ):
372+ bound_range = [bound , bound ]
373+ index = bisect .bisect_left (tag_ranges , bound_range )
374+ if index == len (tag_ranges ):
375+ return tag_ranges , []
376+ left_tags = tag_ranges [:index ]
377+ right_tags = tag_ranges [index :]
378+ match left_tags , right_tags :
379+ case [* _], [[b_start , _], * _] if (
380+ b_start <= bound
381+ ):
382+ return tag_ranges [:index + 1 ], tag_ranges [index + 1 :]
383+ case [* _], [* _]:
384+ return left_tags , right_tags
385+
386+
387+ def partition_by_relevant_bounds (ranges , start , end ):
388+ left , unkwown = partition_by_leftmost (
389+ ranges , start
390+ )
391+ relevant , right = partition_by_rightmost (
392+ unkwown , end
393+ )
394+ return left , relevant , right
0 commit comments