From 04886d3850a7b134432167fd62efd4cfce807e55 Mon Sep 17 00:00:00 2001 From: Alec Marzot Date: Tue, 22 Jun 2021 11:43:09 -0400 Subject: [PATCH 1/3] Use begin and end consistently --- lib/interval_tree.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/interval_tree.rb b/lib/interval_tree.rb index f711bff..261eaee 100644 --- a/lib/interval_tree.rb +++ b/lib/interval_tree.rb @@ -19,9 +19,9 @@ def divide_intervals(intervals) intervals.each do |k| case - when k.last.to_r < x_center + when k.end.to_r < x_center s_left << k - when k.first.to_r > x_center + when k.begin.to_r > x_center s_right << k else s_center << k @@ -38,13 +38,13 @@ def search(query, options = {}) return nil unless @top_node - if query.respond_to?(:first) + if query.respond_to?(:begin) result = top_node.search(query) options[:unique] ? result.uniq : result else point_search(self.top_node, query, [], options[:unique]) end - .sort_by{|x|[x.first, x.last]} + .sort_by{|x|[x.begin, x.end]} end def ==(other) @@ -61,7 +61,7 @@ def ensure_exclusive_end(ranges, range_factory) when range.exclude_end? range else - range_factory.call(range.first, range.end) + range_factory.call(range.begin, range.end) end end end From fe15da9d083bd3ad13db31e4b3683bb230e77104 Mon Sep 17 00:00:00 2001 From: Alec Marzot Date: Tue, 22 Jun 2021 15:37:12 -0400 Subject: [PATCH 2/3] Fix point_search, Add specs, Update README.md Change point_search such that it does not assume the interval has the `include?` method. This allows for custom objects to be used as intervals. Add specs for the behavior of the tree with custom interval objects. Specs include: * Tree creation * Searching for custom objects * Searching with custom objects Update the readme to include examples with custom objects, and update the notes to reflect how custom objects behave. --- README.md | 34 ++++++++++++++++++++++++++++-- lib/interval_tree.rb | 2 +- spec/interval_tree_spec.rb | 43 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 76 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 74f924f..be96053 100644 --- a/README.md +++ b/README.md @@ -49,15 +49,45 @@ p t.search(2, unique: false) #=> [0...3, 0...3, 1...4] p t.search(1...4) #=> [0...3, 1...4, 3...5] ``` +Non-range objects can be used with the tree, as long as they respond to `begin` and `end`. This is useful for storing data along side the intervals. For example: +```ruby +require "interval_tree" + +CS = Struct.new(:begin, :end, :value) # CS = CustomStruct + +itv = [ + CS.new(0,3, "Value"), CS.new(0,3, "Value"), + CS.new(1,4, %w[This is an array]), CS.new(3,5, %w[A second an array]) +] + +t = IntervalTree::Tree.new(itv) +pp t.search(2) +#=> [#, +# #] + +pp t.search(2, unique: false) +#=> [#, +# #, +# #] + +pp t.search(1...4) +#=> [#, +# #, +# #] +``` + ## Note Result intervals are always returned in the "left-closed and right-open" style that can be expressed -by three-dotted Range object literals `(first...last)` +by three-dotted Range object literals `(begin...end)` -Two-dotted full-closed intervals `(first..last)` are also accepted and internally +Two-dotted full-closed intervals `(begin..end)` are also accepted and internally converted to half-closed intervals. +When using custom objects the tree assumes the "left-closed and right-open" style (i.e. the `end` property is not +considered to be inside the interval). + ## Copyright **Author**: MISHIMA, Hiroyuki ( https://github.com/misshie ), Simeon Simeonov ( https://github.com/ssimeonov ), Carlos Alonso ( https://github.com/calonso ), Sam Davies ( https://github.com/samphilipd ), Brendan Weibrecht (https://github.com/ZimbiX), Chris Nankervis (https://github.com/chrisnankervis), Thomas van der Pol (https://github.com/tvanderpol). diff --git a/lib/interval_tree.rb b/lib/interval_tree.rb index 261eaee..3354ca5 100644 --- a/lib/interval_tree.rb +++ b/lib/interval_tree.rb @@ -75,7 +75,7 @@ def center(intervals) def point_search(node, point, result, unique = true) node.s_center.each do |k| - if k.include?(point) + if k.begin <= point && point < k.end result << k end end diff --git a/spec/interval_tree_spec.rb b/spec/interval_tree_spec.rb index f225617..21a4754 100644 --- a/spec/interval_tree_spec.rb +++ b/spec/interval_tree_spec.rb @@ -100,6 +100,16 @@ end end + context 'with custom objects' do + CustomStruct = Struct.new(:begin, :end, :value) + context 'given [CustomStruct.new(1, 6, "value one"), CustomStruct.new(5, 11, "value two")]' do + it 'does not raise an exception' do + itvs = [CustomStruct.new(1, 6, "value one"), CustomStruct.new(5, 11, "value two")] + expect {IntervalTree::Tree.new(itvs)}.not_to raise_exception + end + end + end + context 'with a custom range factory' do class ValueRange < Range attr_accessor :value @@ -328,5 +338,38 @@ def initialize(l, r, value = nil) expect(results).to eq(itvs) end end + + context 'when using custom objects' do + CustomStruct = Struct.new(:begin, :end, :value) + context 'given [CustomStruct.new(1, 6, "value one"), CustomStruct.new(5, 11, "value two")]' do + it 'can search by point' do + itvs = [CustomStruct.new(1, 6, "value one"), CustomStruct.new(5, 11, "value two")] + tree = IntervalTree::Tree.new(itvs) + result = tree.search(2) + expect(result).to be_kind_of Array + item = result.first + expect(item).to be_kind_of CustomStruct + expect(item.value).to be == "value one" + end + + it 'can search by range' do + itvs = [CustomStruct.new(1, 6, "value one"), CustomStruct.new(5, 11, "value two")] + tree = IntervalTree::Tree.new(itvs) + result = tree.search(4...7) + expect(result).to be == itvs + result = tree.search(9...20) + expect(result).to be_kind_of Array + item = result.first + expect(item).to be == CustomStruct.new(5, 11, "value two") + end + + it 'can search by the custom object' do + itvs = [CustomStruct.new(1, 6, "value one"), CustomStruct.new(5, 11, "value two")] + tree = IntervalTree::Tree.new(itvs) + result = tree.search(CustomStruct.new(4,7)) + expect(result).to be == itvs + end + end + end end end From 50a8367049c3c6227caab1fa406fb8429ae580ba Mon Sep 17 00:00:00 2001 From: Brendan Weibrecht Date: Mon, 5 Jul 2021 17:18:24 +1000 Subject: [PATCH 3/3] Switch spec to using stub_const to avoid warning about redefining constant --- spec/interval_tree_spec.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/spec/interval_tree_spec.rb b/spec/interval_tree_spec.rb index 21a4754..c9c3101 100644 --- a/spec/interval_tree_spec.rb +++ b/spec/interval_tree_spec.rb @@ -340,7 +340,10 @@ def initialize(l, r, value = nil) end context 'when using custom objects' do - CustomStruct = Struct.new(:begin, :end, :value) + before do + stub_const('CustomStruct', Struct.new(:begin, :end, :value)) + end + context 'given [CustomStruct.new(1, 6, "value one"), CustomStruct.new(5, 11, "value two")]' do it 'can search by point' do itvs = [CustomStruct.new(1, 6, "value one"), CustomStruct.new(5, 11, "value two")]