| # Licensed to the Apache Software Foundation (ASF) under one | |
| # or more contributor license agreements. See the NOTICE file | |
| # distributed with this work for additional information | |
| # regarding copyright ownership. The ASF licenses this file | |
| # to you under the Apache License, Version 2.0 (the | |
| # "License"); you may not use this file except in compliance | |
| # with the License. You may obtain a copy of the License at | |
| # | |
| # http://www.apache.org/licenses/LICENSE-2.0 | |
| # | |
| # Unless required by applicable law or agreed to in writing, | |
| # software distributed under the License is distributed on an | |
| # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |
| # KIND, either express or implied. See the License for the | |
| # specific language governing permissions and limitations | |
| # under the License. | |
| """ | |
| A helper class to handle ranges. | |
| """ | |
| class RangeHelper: | |
| """A helper class to handle ranges. | |
| Provides methods to check if all ranges are the same and to merge | |
| overlapping ranges into groups, preserving original order within groups. | |
| Args: | |
| range_function: A callable that extracts a Range from an element T. | |
| """ | |
| def __init__(self, range_function): | |
| self._range_function = range_function | |
| def are_all_ranges_same(self, items): | |
| """Check if all items have the same range. | |
| Args: | |
| items: List of items to check. | |
| Returns: | |
| True if all items have the same range, False otherwise. | |
| """ | |
| if not items: | |
| return True | |
| first = items[0] | |
| first_range = self._range_function(first) | |
| if first_range is None: | |
| return False | |
| for item in items[1:]: | |
| if item is None: | |
| return False | |
| current_range = self._range_function(item) | |
| if current_range is None: | |
| return False | |
| if current_range.from_ != first_range.from_ or current_range.to != first_range.to: | |
| return False | |
| return True | |
| def merge_overlapping_ranges(self, items): | |
| """Merge items with overlapping ranges into groups. | |
| Sorts items by range start, then merges overlapping groups. | |
| Within each group, items are sorted by their original index. | |
| Args: | |
| items: List of items with non-null ranges. | |
| Returns: | |
| List of groups, where each group is a list of items | |
| with overlapping ranges. | |
| """ | |
| if not items: | |
| return [] | |
| # Create indexed values to track original indices | |
| indexed = [] | |
| for original_index, item in enumerate(items): | |
| item_range = self._range_function(item) | |
| if item_range is not None: | |
| indexed.append(_IndexedValue(item, item_range, original_index)) | |
| if not indexed: | |
| return [] | |
| # Sort by range start, then by range end | |
| indexed.sort(key=lambda iv: (iv.start(), iv.end())) | |
| groups = [] | |
| current_group = [indexed[0]] | |
| current_end = indexed[0].end() | |
| # Iterate through sorted ranges and merge overlapping ones | |
| for i in range(1, len(indexed)): | |
| current = indexed[i] | |
| if current.start() <= current_end: | |
| current_group.append(current) | |
| if current.end() > current_end: | |
| current_end = current.end() | |
| else: | |
| groups.append(current_group) | |
| current_group = [current] | |
| current_end = current.end() | |
| # Add the last group | |
| groups.append(current_group) | |
| # Convert groups to result, sorting each group by original index | |
| result = [] | |
| for group in groups: | |
| group.sort(key=lambda iv: iv.original_index) | |
| result.append([iv.value for iv in group]) | |
| return result | |
| class _IndexedValue: | |
| """A helper class to track original indices during range merging.""" | |
| def __init__(self, value, item_range, original_index): | |
| self.value = value | |
| self.range = item_range | |
| self.original_index = original_index | |
| def start(self): | |
| return self.range.from_ | |
| def end(self): | |
| return self.range.to |