Skip to content

Commit 53254ce

Browse files
committed
Merge remote-tracking branch 'origin/premature_optimization/tag_ranges' into tag_capable+premature_optimizations
2 parents 6985ab7 + 43df4db commit 53254ce

File tree

2 files changed

+236
-133
lines changed

2 files changed

+236
-133
lines changed

kytos/core/tag_ranges.py

Lines changed: 226 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
"""Methods for list of ranges [inclusive, inclusive]"""
22
# pylint: disable=too-many-branches
33
import 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

77
from kytos.core.exceptions import KytosInvalidTagRanges
88

@@ -94,9 +94,8 @@ def get_validated_tags(
9494
def 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

140152
def 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

194242
def 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

261318
def 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

Comments
 (0)