From f9aa21b14c6268bfaf4dda6eb8822d224efe607f Mon Sep 17 00:00:00 2001 From: Valentin Lorentz Date: Tue, 8 Mar 2016 11:21:17 +0100 Subject: [PATCH 1/6] Add method first_after. --- intervaltree/intervaltree.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/intervaltree/intervaltree.py b/intervaltree/intervaltree.py index 3eda476..df189da 100644 --- a/intervaltree/intervaltree.py +++ b/intervaltree/intervaltree.py @@ -762,6 +762,39 @@ def items(self): :rtype: set of Interval """ return set(self.all_intervals) + + def first_after(self, value): + """Return an interval whose beginning is ≥ value and is minimal.""" + node = self.top_node + first = None + key = lambda iv: iv.begin + if not node: + raise ValueError('Empty IntervalTree.') + + def update_first(first, ivs): + try: + min_ = min((iv for iv in ivs if iv.begin >= value), key=key) + except ValueError: + pass + else: + if first is None or first.begin > min_: + first = min_ + return first + + if node.x_center < value: + while node.x_center < value: + first = update_first(first, node.s_center) + node = node.right_node + else: + while node.x_center >= value: + first = update_first(first, node.s_center) + node = node.left_node + candidates = [iv for iv in node.all_children() if iv.begin >= value] + if first: + candidates.append(first) + return min(candidates, key=key) + + def is_empty(self): """ From cad7b334450b848a20ab6cbdd70231b6b4e17122 Mon Sep 17 00:00:00 2001 From: Valentin Lorentz Date: Tue, 8 Mar 2016 12:51:19 +0100 Subject: [PATCH 2/6] Add method first_before. --- intervaltree/intervaltree.py | 37 +++++++++++++++++++++++++----------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/intervaltree/intervaltree.py b/intervaltree/intervaltree.py index df189da..579085f 100644 --- a/intervaltree/intervaltree.py +++ b/intervaltree/intervaltree.py @@ -763,36 +763,51 @@ def items(self): """ return set(self.all_intervals) - def first_after(self, value): + def _first_after_or_after(self, value, key, optimum, compare): """Return an interval whose beginning is ≥ value and is minimal.""" node = self.top_node first = None - key = lambda iv: iv.begin if not node: raise ValueError('Empty IntervalTree.') def update_first(first, ivs): try: - min_ = min((iv for iv in ivs if iv.begin >= value), key=key) + opt = optimum((iv for iv in ivs if compare(iv.begin, value)), key=key) except ValueError: pass else: - if first is None or first.begin > min_: - first = min_ + if first is None or compare(key(first), key(opt)): + first = opt return first if node.x_center < value: - while node.x_center < value: + while node is not None and not compare(node.x_center, value): first = update_first(first, node.s_center) node = node.right_node else: - while node.x_center >= value: + while node is not None and compare(node.x_center, value): first = update_first(first, node.s_center) node = node.left_node - candidates = [iv for iv in node.all_children() if iv.begin >= value] - if first: - candidates.append(first) - return min(candidates, key=key) + if node is None: + return first + else: + candidates = [iv for iv in node.all_children() if compare(key(iv), value)] + if first: + candidates.append(first) + return optimum(candidates, key=key) + + def first_after(self, value): + return self._first_after_or_after(value, + key=lambda iv: iv.begin, + optimum=min, + compare=lambda x, y: x >= y) + + def first_before(self, value): + """Return an interval whose end is ≤ value and is maximal.""" + return self._first_after_or_after(value, + key=lambda iv: iv.end, + optimum=max, + compare=lambda x, y: x <= y) From 88a14404b3dea843d8a9a0bfa2fc745044edd598 Mon Sep 17 00:00:00 2001 From: Valentin Lorentz Date: Tue, 8 Mar 2016 12:52:08 +0100 Subject: [PATCH 3/6] Add tests for first_before and first_after. --- .../first_before_after_test.py | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 test/intervaltree_methods/first_before_after_test.py diff --git a/test/intervaltree_methods/first_before_after_test.py b/test/intervaltree_methods/first_before_after_test.py new file mode 100644 index 0000000..c1aaa66 --- /dev/null +++ b/test/intervaltree_methods/first_before_after_test.py @@ -0,0 +1,31 @@ +from __future__ import absolute_import +from intervaltree import Interval, IntervalTree +import pytest +from test.intervaltrees import trees, sdata + +def test_first_after(): + t = trees['ivs1']() + assert t.first_after(7) in [ + Interval(8, 10, '[8,10)'), + Interval(8, 15, '[8,15)'), + ] + assert t.first_after(8) in [ + Interval(8, 10, '[8,10)'), + Interval(8, 15, '[8,15)'), + ] + assert t.first_after(13) == Interval(14, 15, '[14,15)') + assert t.first_after(4) == Interval(4, 7, '[4,7)') + assert t.first_after(5) == Interval(5, 9, '[5,9)') + +def test_first_before(): + t = trees['ivs1']() + assert t.first_before(5) == Interval(1, 2, '[1,2)') + assert t.first_before(7) == Interval(4, 7, '[4,7)') + assert t.first_before(10) in [ + Interval(6, 10, '[6,10)'), + Interval(8, 10, '[8,10)'), + ] + assert t.first_before(15) == Interval(14, 15, '[14,15)') + +if __name__ == "__main__": + pytest.main([__file__, '-v']) From 804b188a7cac01368a5ed9537af466dd43045646 Mon Sep 17 00:00:00 2001 From: Valentin Lorentz Date: Tue, 8 Mar 2016 12:53:28 +0100 Subject: [PATCH 4/6] Fix doc for first_after. --- intervaltree/intervaltree.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/intervaltree/intervaltree.py b/intervaltree/intervaltree.py index 579085f..988b2ab 100644 --- a/intervaltree/intervaltree.py +++ b/intervaltree/intervaltree.py @@ -764,7 +764,6 @@ def items(self): return set(self.all_intervals) def _first_after_or_after(self, value, key, optimum, compare): - """Return an interval whose beginning is ≥ value and is minimal.""" node = self.top_node first = None if not node: @@ -797,6 +796,7 @@ def update_first(first, ivs): return optimum(candidates, key=key) def first_after(self, value): + """Return an interval whose beginning is ≥ value and is minimal.""" return self._first_after_or_after(value, key=lambda iv: iv.begin, optimum=min, From df83704807f7701b7570bb39931d9d9cb0dc637d Mon Sep 17 00:00:00 2001 From: Valentin Lorentz Date: Tue, 8 Mar 2016 13:55:58 +0100 Subject: [PATCH 5/6] Raise ValueError if no interval is found instead of returning None. --- intervaltree/intervaltree.py | 5 ++++- .../first_before_after_test.py | 14 ++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/intervaltree/intervaltree.py b/intervaltree/intervaltree.py index 988b2ab..dfb37f2 100644 --- a/intervaltree/intervaltree.py +++ b/intervaltree/intervaltree.py @@ -788,7 +788,10 @@ def update_first(first, ivs): first = update_first(first, node.s_center) node = node.left_node if node is None: - return first + if first: + return first + else: + raise ValueError('No interval after {0}.'.format(value)) else: candidates = [iv for iv in node.all_children() if compare(key(iv), value)] if first: diff --git a/test/intervaltree_methods/first_before_after_test.py b/test/intervaltree_methods/first_before_after_test.py index c1aaa66..2c273b8 100644 --- a/test/intervaltree_methods/first_before_after_test.py +++ b/test/intervaltree_methods/first_before_after_test.py @@ -16,6 +16,13 @@ def test_first_after(): assert t.first_after(13) == Interval(14, 15, '[14,15)') assert t.first_after(4) == Interval(4, 7, '[4,7)') assert t.first_after(5) == Interval(5, 9, '[5,9)') + assert t.first_after(-100) == Interval(1, 2, '[1,2)') + try: + iv = t.first_after(15) + except Exception as e: + assert isinstance(e, ValueError) + else: + assert False, iv def test_first_before(): t = trees['ivs1']() @@ -26,6 +33,13 @@ def test_first_before(): Interval(8, 10, '[8,10)'), ] assert t.first_before(15) == Interval(14, 15, '[14,15)') + assert t.first_before(100) == Interval(14, 15, '[14,15)') + try: + iv = t.first_before(1) + except Exception as e: + assert isinstance(e, ValueError) + else: + assert False, iv if __name__ == "__main__": pytest.main([__file__, '-v']) From b6228792333774ea4cab1719fe267bfda35122fd Mon Sep 17 00:00:00 2001 From: Valentin Lorentz Date: Fri, 25 Mar 2016 11:03:58 +0100 Subject: [PATCH 6/6] Better exception message if no interval found. --- intervaltree/intervaltree.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/intervaltree/intervaltree.py b/intervaltree/intervaltree.py index dfb37f2..955bb46 100644 --- a/intervaltree/intervaltree.py +++ b/intervaltree/intervaltree.py @@ -791,11 +791,13 @@ def update_first(first, ivs): if first: return first else: - raise ValueError('No interval after {0}.'.format(value)) + raise ValueError('No interval before/after {0}.'.format(value)) else: candidates = [iv for iv in node.all_children() if compare(key(iv), value)] if first: candidates.append(first) + if not candidates: + raise ValueError('No interval before/after {0}.'.format(value)) return optimum(candidates, key=key) def first_after(self, value):