Skip to content

Commit

Permalink
Merge pull request #184 from octoenergy/optimise-xocto-ranges-any-ove…
Browse files Browse the repository at this point in the history
…rlaps-any-gaps

Optimise `ranges.any_overlapping`
  • Loading branch information
Peter554 authored Jan 13, 2025
2 parents b72c14c + 2670ac0 commit 4bef5b5
Show file tree
Hide file tree
Showing 8 changed files with 36 additions and 5 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
.benchmarks/

# Byte-compiled / optimized files
*.py[cod]
__pycache__
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

## Unreleased

- Improve the performance of the `ranges.any_overlapping` function
(with some benchmark showing a >100x speed up) [#184](https://github.com/octoenergy/xocto/pull/184).

## V7.0.0 - 2024-12-19

- Add `FiniteDatetimeRange.as_date_range` [#181](https://github.com/octoenergy/xocto/pull/181)
Expand Down
5 changes: 4 additions & 1 deletion makefile
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ lint_check:
ruff check .

test:
py.test
py.test --benchmark-skip

benchmark:
py.test --benchmark-only --benchmark-autosave --benchmark-compare

mypy:
mypy
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ dev = [
"pre-commit>=3.7.1",
"psycopg2>=2.8.4",
"pyarrow-stubs==10.0.1.6",
"pytest-benchmark==5.0.1",
"pytest-django==4.8.0",
"pytest-mock==3.12.0",
"pytest==8.0.2",
Expand Down
Empty file added tests/benchmarks/__init__.py
Empty file.
13 changes: 13 additions & 0 deletions tests/benchmarks/test_ranges.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import random
from decimal import Decimal as D

from xocto import ranges


def test_any_overlapping(benchmark):
ranges_ = [ranges.Range(D(i), D(i + 1)) for i in range(1000)]
random.seed(42)
random.shuffle(ranges_)

any_overlapping = benchmark(ranges.any_overlapping, ranges_)
assert any_overlapping is False
7 changes: 7 additions & 0 deletions tests/test_ranges.py
Original file line number Diff line number Diff line change
Expand Up @@ -596,6 +596,13 @@ class TestAnyOverlapping:
ranges.Range(0, 2),
ranges.Range(1, 3),
],
[
ranges.Range(0, 2),
ranges.Range(
4, 5
), # Added this to ensure the overlapping ranges are not adjacent.
ranges.Range(1, 3),
],
[
ranges.Range(
0, 2, boundaries=ranges.RangeBoundaries.INCLUSIVE_INCLUSIVE
Expand Down
10 changes: 6 additions & 4 deletions xocto/ranges.py
Original file line number Diff line number Diff line change
Expand Up @@ -1054,14 +1054,16 @@ def get_finite_datetime_ranges_from_timestamps(

def any_overlapping(ranges: Iterable[Range[T]]) -> bool:
"""Return true if any of the passed Ranges are overlapping."""
ranges = list(ranges)
# We're deliberately not using RangeSet here for better performance.
# See https://github.com/octoenergy/xocto/pull/184.
if not ranges:
return False
range_set = RangeSet[T]([ranges[0]])
ranges = sorted(ranges)
prev_range: Range[T] = ranges[0]
for range in ranges[1:]:
if range_set.intersection(range):
if prev_range.intersection(range):
return True
range_set.add(range)
prev_range = range
return False


Expand Down

0 comments on commit 4bef5b5

Please sign in to comment.