Skip to content

Allow use with simple custom objects #13

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
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
34 changes: 32 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
#=> [#<struct CS begin=0, end=3, value="Value">,
# #<struct CS begin=1, end=4, value=["This", "is", "an", "array"]>]

pp t.search(2, unique: false)
#=> [#<struct CS begin=0, end=3, value="Value">,
# #<struct CS begin=0, end=3, value="Value">,
# #<struct CS begin=1, end=4, value=["This", "is", "an", "array"]>]

pp t.search(1...4)
#=> [#<struct CS begin=0, end=3, value="Value">,
# #<struct CS begin=1, end=4, value=["This", "is", "an", "array"]>,
# #<struct CS begin=3, end=5, value=["A", "second", "an", "array"]>]
```

## 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).
Expand Down
12 changes: 6 additions & 6 deletions lib/interval_tree.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand All @@ -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
Expand All @@ -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
Expand Down
46 changes: 46 additions & 0 deletions spec/interval_tree_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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