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