Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
- Better subclassing support: Determine classes dynamically,
so that methods like str() are aware when our types are subclassed.
- Removed no-op check and extra list allocation during chop()
- overlaps() on a range no longer scans the entire boundary table;
only boundary points inside the queried range are tested (#127)
- Maintainers:
- Load issue4 test data via JSON to save on compilation and collection time.

Expand Down
10 changes: 8 additions & 2 deletions intervaltree/intervaltree.py
Original file line number Diff line number Diff line change
Expand Up @@ -619,10 +619,16 @@ def overlaps_range(self, begin, end):
return False
elif self.overlaps_point(begin):
return True
# only test boundary points inside (begin, end), not the whole
# boundary table (#127); boundaries exactly at begin are already
# covered by the overlaps_point(begin) check above
boundary_table = self.boundary_table
bound_begin = boundary_table.bisect_left(begin)
bound_end = boundary_table.bisect_left(end) # up to, but not including end
return any(
self.overlaps_point(bound)
for bound in self.boundary_table
if begin < bound < end
for index in xrange(bound_begin, bound_end)
for bound in (boundary_table.keys()[index],)
)

def split_overlaps(self):
Expand Down
28 changes: 28 additions & 0 deletions test/issues/issue127_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-
"""
Regression test for issue #127: overlaps(begin, end) on a range scanned
every boundary point in the tree (O(n) pure-python comparisons per query)
instead of only the points inside (begin, end).

300k disjoint intervals -> 600k boundary points. A miss-case query in the
unfixed code takes ~4s for 200 queries; with the boundary scan restricted
to the query range it should be under a tenth of a second.
"""
import time

from intervaltree import IntervalTree


def test_overlaps_range_scans_only_query_range():
tree = IntervalTree.from_tuples((i * 10, i * 10 + 4) for i in range(300000))

# sanity: semantics must be unchanged
assert tree.overlaps(500006, 500009) is False # range inside a pure gap
assert tree.overlaps(500006, 500012) is True # range reaches (500010, 500014)

t0 = time.time()
for _ in range(200):
tree.overlaps(500006, 500009)
# unfixed: ~4s (600k boundary comparisons per query)
# fixed: ~0.01s (only the 0-2 boundaries inside the range)
assert time.time() - t0 < 1.0