Skip to content

Commit

Permalink
all test passed so far
Browse files Browse the repository at this point in the history
  • Loading branch information
misshie committed Jul 11, 2011
1 parent 827cd2a commit 38d4292
Show file tree
Hide file tree
Showing 2 changed files with 131 additions and 75 deletions.
91 changes: 53 additions & 38 deletions lib/interval_tree.rb
Original file line number Diff line number Diff line change
@@ -1,27 +1,32 @@
#!/usr/local/bin/ruby-1.9
#
# Title: the IntervalTree module
# Author: MISHIMA, Hiroyuki ( https://github.com/misshie )
#
# Original code written in Python that is available at
# http://forrst.com/posts/Interval_Tree_implementation_in_python-e0K
#
# Title:: the IntervalTree module using "argumanted tree"
# Author:: MISHIMA, Hiroyuki ( https://github.com/misshie ), 2011
# Copyright:: The MIT/X11 license
#
# see also ....
# description in Wikipedia
# http://en.wikipedia.org/wiki/Interval_tree
# implementstion in Python by Tyler Kahn
# http://forrst.com/posts/Interval_Tree_implementation_in_python-e0K
#
# Usage:
# require "interval_tree"
# include IntervalTree
# itv = [Interval.new(0,3), Interval.new(1,4), Interval.new(3,5),]
# t = Tree.new(itv)
# t.search(2).each{|x|pus "#{x.first}-#{x.last}"} # => 1-4
# t.search(1,3).each{|x|puts "#{x.first}-#{x.last}"} #=> 1-4, 3-5
# itv = [(0...3), (1...4), (3...5),]
# t = IntervalTree::Tree.new(itv)
# p t.search(2) => [0...3, 1...4]
# p t.search(1...3) => [0...3, 1...4, 3...5]
#
# note: intevels are "left-closed and right-open, i.e. [first, last)"
# note: intevels are expected to be "left-closed and right-open"
# that can be expressed by Range object literals (first...last)
#

module IntervalTree

class Tree
def initialize(intervals)
@top_node = divide_intervals(intervals)
def initialize(ranges)
ranges_excl = ensure_exclusive_end([ranges].flatten)
@top_node = divide_intervals(ranges_excl)
end
attr_reader :top_node

Expand All @@ -46,62 +51,72 @@ def divide_intervals(intervals)
divide_intervals(s_left), divide_intervals(s_right))
end

def center(intervals)
fs = intervals.sort_by{|x|x.first}
fs[fs.length/2].first
end

def search(first, last = nil)
def search(interval)
if interval.respond_to?(:first)
first = interval.first
last = interval.last
else
first = interval
last = nil
end

if last
result = Array.new
(first..last+1).each do |j|
search(j).each{|k|result << k}
result.uniq!
end
result.sort_by{|x|x.first}
result.sort_by{|x|[x.first, x.last]}
else
point_search(self.top_node, first, [])
point_search(self.top_node, first, []).sort_by{|x|[x.first, x.last]}
end
end

private

def ensure_exclusive_end(ranges)
ranges.map do |range|
if range.exclude_end?
range
else
(range.first ... range.end+1)
end
end
end

# argumented tree
# using a start point as resresentative value of the node
def center(intervals)
fs = intervals.sort_by{|x|x.first}
fs[fs.length/2].first
end

def point_search(node, point, result)
node.s_center.each do |k|
result << k if (k.first <= point) && (point <= k.last)
end
if (point < node.x_center) && node.left_node
if node.left_node && ( point <= node.left_node.s_max )
point_search(node.left_node, point, []).each{|k|result << k}
end
if (node.x_center < point) && node.right_node
if node.right_node && ( node.right_node.x_center <= point )
point_search(node.right_node, point, []).each{|k|result << k}
end
result.uniq
end
end # class Tree

class Interval
def initialize(first, last)
@first = first
@last = last
end
attr_reader :first, :last
alias :begin :first
alias :end :last
end # class Interval

class Node
def initialize(x_center, s_center, left_node, right_node)
@x_center = x_center
@s_center = s_center.sort_by{|x|x.first}
# @s_center_first = s_center.sort_by{|x|x.first}
# @s_center_last = s_center.sort_by{|x|x.last}
@s_center = s_center.sort_by(&:first)
@s_max = s_center.map(&:last).max
@left_node = left_node
@right_node = right_node
end
attr_reader :x_center, :s_center, :left_node, :right_node
attr_reader :x_center, :s_center, :s_max, :left_node, :right_node
end # class Node

end # module IntervalTree

if __FILE__ == $0
Expand Down
115 changes: 78 additions & 37 deletions spec/interval_tree_spec.rb
Original file line number Diff line number Diff line change
@@ -1,26 +1,6 @@
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
require 'interval_tree'

describe "IntervalTree::Interval" do

describe ".new" do
context 'given (0,10)' do
it 'returns interval object' do
IntervalTree::Interval.new(0, 10).should be_a IntervalTree::Interval
end
end
end

describe ".first" do
context 'given (20,30)' do
it 'returns 20' do
IntervalTree::Interval.new(20, 30).first.should == 20
end
end
end

end

describe "IntervalTree::Node" do

describe '.new' do
Expand All @@ -35,32 +15,93 @@

describe "IntervalTree::Tree" do

describe '#center' do
context 'given [(1...5),]' do
it 'returns 2' do
itvs = [(1...5),]
t = IntervalTree::Tree.new([])
t.__send__(:center, itvs).should == 1

end
end

context 'given [(1...5), (2...6)]' do
it 'returns 2' do
itvs = [(1...5), (2...6),]
t = IntervalTree::Tree.new([])
t.__send__(:center, itvs).should == 2
end
end
end

describe '.new' do
context 'given [Interval.new(1, 5)]' do
it 'returns an Tree objects' do
itvs = [IntervalTree::Interval.new(1, 5),]
IntervalTree::Tree.new(itvs).should be_a(IntervalTree::Tree)
context 'given [(1...5),]' do
it 'returns an Tree objects' do
itvs = [(1...5),]
IntervalTree::Tree.new(itvs).should be_an IntervalTree::Tree
end
end

context 'given [(1...5),(2...6), (3...7)]' do
it 'returns ret.top_node.x_centeran == 2 ' do
itvs = [(1...5),(2...6), (3...7)]
tree = IntervalTree::Tree.new(itvs)
tree.top_node.x_center.should == 2
end
end
end

describe '.search' do
context 'given an array of a interval (1,5] and a point query "3"' do
it 'returns an array of intervals (1,5]' do
itvs = [IntervalTree::Interval.new(1, 5),]
tree = IntervalTree::Tree.new(itvs).search(3)
tree.should == itvs
describe '#search' do
context 'given [(1...5)] a point query "3"' do
it 'returns an array of intervals (1...5)]' do
IntervalTree::Tree.new([1...5]).search(3).should == [1...5]
end
end

context 'given non-array full-closed "(1..4)" and a point query "3"' do
it 'returns an array contains a half-open interval (1...5)]' do
IntervalTree::Tree.new(1..4).search(3).should == [1...5]
end
end

context 'given [(1...5), (2...6)] and a point query "3"' do
it 'returns [(1...5), (2...6)]' do
itvs = [(1...5), (2...6),]
results = IntervalTree::Tree.new(itvs).search(3)
results.should == itvs
end
end

context 'given [(0...8), (1...5), (2...6)] and a point query "3"' do
it 'returns [(0...8), (1...5), (2...6)]' do
itvs = [(0...8), (1...5), (2...6)]
results = IntervalTree::Tree.new(itvs).search(3)
results.should == itvs
end
end

context 'given an array of a interval (1,5]/(2,6] and a point query "3"' do
it 'returns an array of intervals (1,5]/(2,6]' do
itvs = [IntervalTree::Interval.new(1, 5),
IntervalTree::Interval.new(2, 6),]
tree = IntervalTree::Tree.new(itvs).search(3)
tree.should == itvs
context 'given [(0...8), (1...5), (2...6)] and a query by (1...4)' do
it 'returns [(0...8), (1...5), (2...6)]' do
itvs = [(0...8), (1...5), (2...6)]
results = IntervalTree::Tree.new(itvs).search(1...4)
results.should == itvs
end
end

# context 'given [(0...8), (1...5), (2...6)] and a query by (1...4)' do
# it 'returns [(0...8), (1...5), (2...6)]' do
# itvs = [(0...8), (1...5), (2...6)]
# results = IntervalTree::Tree.new(itvs).search(1...4)
# results.should == itvs
# end
# end






end

end
end

0 comments on commit 38d4292

Please sign in to comment.