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 f711bff..3354ca5 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 @@ -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..c9c3101 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,41 @@ def initialize(l, r, value = nil) expect(results).to eq(itvs) end end + + context 'when using custom objects' do + 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")] + 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