From 6ab7af98493aaeb77160ea0f0b7bddfbdf50153e Mon Sep 17 00:00:00 2001 From: Daniel Petri Rocha <33319791+danielpetri1@users.noreply.github.com> Date: Mon, 28 Jun 2021 16:02:49 +0200 Subject: [PATCH 01/12] Fixes point_search when unique is false This change implements point_search iteratively to ensure a stack overflow can not occur. It also makes the method use the passed unique parameter, as the recursive call was previously defaulting unique to true: itv = [(5...20), (15.6...20), (15.7...20), (15.7...20)] t = IntervalTree::Tree.new(itv) p t.search(15.7) #=> [5...20, 15.6...20, 15.7...20] p t.search(15.7, unique: false) #=> Should be [5...20, 15.6...20, 15.7...20, 15.7...20], not [5...20, 15.6...20, 15.7...20] again Additionally, result.uniq is invoked at the end of the method for faster query speeds. --- lib/interval_tree.rb | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/lib/interval_tree.rb b/lib/interval_tree.rb index f711bff..7540ff6 100644 --- a/lib/interval_tree.rb +++ b/lib/interval_tree.rb @@ -73,18 +73,25 @@ def center(intervals) ) / 2 end - def point_search(node, point, result, unique = true) - node.s_center.each do |k| - if k.include?(point) - result << k + def point_search(node, point, result, unique) + stack = [node] + + until stack.empty? + node = stack.pop + + node.s_center.each do |k| + if k.begin <= point && point < k.end + result << k + end + end + if node.left_node && ( point.to_r < node.x_center ) + stack.push(node.left_node) + + elsif node.right_node && ( point.to_r >= node.x_center ) + stack.push(node.right_node) + end + end - end - if node.left_node && ( point.to_r < node.x_center ) - point_search(node.left_node, point, []).each{|k|result << k} - end - if node.right_node && ( point.to_r >= node.x_center ) - point_search(node.right_node, point, []).each{|k|result << k} - end if unique result.uniq else From b6488cb331beda716d58bd668c29d35f316ea4ac Mon Sep 17 00:00:00 2001 From: Daniel Petri Rocha <33319791+danielpetri1@users.noreply.github.com> Date: Mon, 28 Jun 2021 16:10:08 +0200 Subject: [PATCH 02/12] Update interval_tree.rb Fixed point_search Indentation --- lib/interval_tree.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/interval_tree.rb b/lib/interval_tree.rb index 7540ff6..8be2872 100644 --- a/lib/interval_tree.rb +++ b/lib/interval_tree.rb @@ -91,7 +91,7 @@ def point_search(node, point, result, unique) stack.push(node.right_node) end - end + end if unique result.uniq else From a043f3415858c4505b7b687bff5b58fbe8a862b7 Mon Sep 17 00:00:00 2001 From: Daniel Petri Rocha <33319791+danielpetri1@users.noreply.github.com> Date: Mon, 28 Jun 2021 18:03:49 +0200 Subject: [PATCH 03/12] << is faster than push Co-authored-by: amarzot-yesware <83241806+amarzot-yesware@users.noreply.github.com> --- lib/interval_tree.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/interval_tree.rb b/lib/interval_tree.rb index 8be2872..53dba04 100644 --- a/lib/interval_tree.rb +++ b/lib/interval_tree.rb @@ -85,10 +85,10 @@ def point_search(node, point, result, unique) end end if node.left_node && ( point.to_r < node.x_center ) - stack.push(node.left_node) + stack << node.left_node elsif node.right_node && ( point.to_r >= node.x_center ) - stack.push(node.right_node) + stack << node.right_node end end From c13223cec050626608c603f12a24a0582b4e8480 Mon Sep 17 00:00:00 2001 From: Daniel Petri Rocha <33319791+danielpetri1@users.noreply.github.com> Date: Fri, 2 Jul 2021 13:44:53 +0200 Subject: [PATCH 04/12] Update interval_tree_spec.rb --- spec/interval_tree_spec.rb | 45 +++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/spec/interval_tree_spec.rb b/spec/interval_tree_spec.rb index f225617..dd6077b 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 @@ -139,7 +149,7 @@ def initialize(l, r, value = nil) let(:node) do IntervalTree::Node.new( 12.5, # x_center - [ 10...14, 2...20 ], # s_center + [ 2...20, 10...14 ], # s_center left_node, # s_left right_node, # s_right ) @@ -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 2b7d84b5298798d86dfb3dc453261234b4948af1 Mon Sep 17 00:00:00 2001 From: Daniel Petri Rocha Date: Thu, 16 Sep 2021 22:47:18 +0200 Subject: [PATCH 05/12] Undo linting changes, undo sorted parameter As requested, linting changes are now separate from functionality changes to make reviewing easier. The sorted parameter was removed since I believe it was redundantly sorting the output array, which is probably why it was slower than the current implementation. --- lib/interval_tree.rb | 40 ++++++++++++++++++++++------------- spec/interval_tree_spec.rb | 43 ++++++++++++++++++++++++++++++++++---- 2 files changed, 65 insertions(+), 18 deletions(-) diff --git a/lib/interval_tree.rb b/lib/interval_tree.rb index 53dba04..eb6d5d1 100644 --- a/lib/interval_tree.rb +++ b/lib/interval_tree.rb @@ -19,32 +19,35 @@ 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 end end + + s_center.sort_by! { |x| [x.begin, x.end] } + Node.new(x_center, s_center, divide_intervals(s_left), divide_intervals(s_right)) end # Search by range or point - DEFAULT_OPTIONS = {unique: true} + DEFAULT_OPTIONS = { unique: true } def search(query, options = {}) options = DEFAULT_OPTIONS.merge(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 + options[:unique] ? result.uniq! : result else - point_search(self.top_node, query, [], options[:unique]) + result = point_search(self.top_node, query, [], options[:unique]) end - .sort_by{|x|[x.first, x.last]} + result end def ==(other) @@ -61,7 +64,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,19 +78,20 @@ def center(intervals) def point_search(node, point, result, unique) stack = [node] + point_r = point.to_r until stack.empty? node = stack.pop node.s_center.each do |k| - if k.begin <= point && point < k.end - result << k - end + break if k.begin > point + result << k if point < k.end end - if node.left_node && ( point.to_r < node.x_center ) + + if node.left_node && ( point_r < node.x_center ) stack << node.left_node - elsif node.right_node && ( point.to_r >= node.x_center ) + elsif node.right_node && ( point_r >= node.x_center ) stack << node.right_node end @@ -126,7 +130,12 @@ def search(query) private def search_s_center(query) - s_center.select do |k| + result = [] + + s_center.each do |k| + break if k.begin > query.end + + next unless ( # k is entirely contained within the query (k.begin >= query.begin) && @@ -144,7 +153,10 @@ def search_s_center(query) (k.begin < query.begin) && (k.end > query.end) ) + result << k end + + result end end # class Node diff --git a/spec/interval_tree_spec.rb b/spec/interval_tree_spec.rb index dd6077b..5d66da3 100644 --- a/spec/interval_tree_spec.rb +++ b/spec/interval_tree_spec.rb @@ -285,10 +285,20 @@ def initialize(l, r, value = nil) expect(results).to be == [(3...5), (3...9), (4...8)] end end + + context 'with unique defaulting to true, given intervals with duplicates' do + context 'given [(1...3), (3...5), (3...9), (4...8), (4...8)] and a query by (3...5)]' do + it 'removes the duplicates in the result' do + itv = [(1...3), (3...5), (3...9), (4...8), (4...8)] + t = IntervalTree::Tree.new(itv) + results = t.search((3...5)) + + expect(results).to match_array([(3...5), (3...9), (4...8)]) + end + end - context 'with unique defaulting to true' do - context 'given intervals with duplicates' do - it 'returns the duplicates in the result' do + context 'given [(0...3), (1...4), (3...5), (0...3)] and a query by (2)' do + it 'removes the duplicates in the result' do itv = [(0...3), (1...4), (3...5), (0...3)] t = IntervalTree::Tree.new(itv) results = t.search(2) @@ -313,6 +323,31 @@ def initialize(l, r, value = nil) expect(results).to match_array([(2...4)]) end end + + context 'given [(0...5), (1...5), (3...5), (3...5)] and a query by (3)' do + it 'returns [(0...5), (1...5), (3...5), (3...5)]' do + itvs = [(0...5), (1...5), (3...5), (3...5)] + results = IntervalTree::Tree.new(itvs).search(3, unique: false) + expect(results).to match_array([(0...5), (1...5), (3...5), (3...5)]) + end + end + + context 'given [(0...3), (1...4), (3...4), (3...4), (3...5)] and a query by (3)' do + it 'returns [(1...4), (3...4), (3...4), (3...5)]' do + itvs = [(0...3), (1...4), (3...4), (3...4), (3...5)] + results = IntervalTree::Tree.new(itvs).search(3, unique: false) + expect(results).to match_array([(1...4), (3...4), (3...4), (3...5)]) + end + end + + context 'given [(0...2), (0...2), (1...2), (1...2), (2...5)] and a query by (1)' do + it 'returns [(0...2), (0...2), (1...2), (1...2)]' do + itvs = [(0...2), (0...2), (1...2), (1...2), (2...5)] + results = IntervalTree::Tree.new(itvs).search(1, unique: false) + expect(results).to match_array([(0...2), (0...2), (1...2), (1...2)]) + end + end + end context "when concerned with performance" do @@ -372,4 +407,4 @@ def initialize(l, r, value = nil) end end end -end +end \ No newline at end of file From 44900ec1a9005ee53b3999984ca8fc391b659c74 Mon Sep 17 00:00:00 2001 From: Daniel Petri Rocha Date: Thu, 16 Sep 2021 23:17:09 +0200 Subject: [PATCH 06/12] Removed duplicate method calls with Reek --- lib/interval_tree.rb | 37 ++++++++++++++++++++++++------------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/lib/interval_tree.rb b/lib/interval_tree.rb index eb6d5d1..7160ba4 100644 --- a/lib/interval_tree.rb +++ b/lib/interval_tree.rb @@ -82,17 +82,20 @@ def point_search(node, point, result, unique) until stack.empty? node = stack.pop + node_left_node = node.left_node + node_right_node = node.right_node + node_x_center = node.x_center node.s_center.each do |k| break if k.begin > point result << k if point < k.end end - if node.left_node && ( point_r < node.x_center ) - stack << node.left_node + if node_left_node && ( point_r < node_x_center ) + stack << node_left_node - elsif node.right_node && ( point_r >= node.x_center ) - stack << node.right_node + elsif node_right_node && ( point_r >= node_x_center ) + stack << node_right_node end end @@ -133,25 +136,33 @@ def search_s_center(query) result = [] s_center.each do |k| - break if k.begin > query.end + k_begin = k.begin + query_end = query.end + break if k_begin > query_end + + k_end = k.end + query_begin = query.begin + + k_begin_gte_q_begin = k_begin >= query_begin + k_end_lte_q_end = k_end <= query_end next unless ( # k is entirely contained within the query - (k.begin >= query.begin) && - (k.end <= query.end) + (k_begin_gte_q_begin) && + (k_end_lte_q_end) ) || ( # k's start overlaps with the query - (k.begin >= query.begin) && - (k.begin < query.end) + (k_begin_gte_q_begin) && + (k_begin < query_end) ) || ( # k's end overlaps with the query - (k.end > query.begin) && - (k.end <= query.end) + (k_end > query_begin) && + (k_end_lte_q_end) ) || ( # k is bigger than the query - (k.begin < query.begin) && - (k.end > query.end) + (k_begin < query_begin) && + (k_end > query_end) ) result << k end From 8e2927b2d3969719059dd4025bc9e94266afe7c3 Mon Sep 17 00:00:00 2001 From: Daniel Petri Rocha Date: Thu, 16 Sep 2021 23:19:19 +0200 Subject: [PATCH 07/12] Linting with Rubocop --- lib/interval_tree.rb | 58 ++++++++++++++++++++++---------------------- 1 file changed, 29 insertions(+), 29 deletions(-) mode change 100644 => 100755 lib/interval_tree.rb diff --git a/lib/interval_tree.rb b/lib/interval_tree.rb old mode 100644 new mode 100755 index 7160ba4..78bc9ab --- a/lib/interval_tree.rb +++ b/lib/interval_tree.rb @@ -1,10 +1,10 @@ #!/usr/bin/env ruby +# frozen_string_literal: true module IntervalTree - class Tree def initialize(ranges, &range_factory) - range_factory = lambda { |l, r| (l ... r+1) } unless block_given? + range_factory = ->(l, r) { (l...r + 1) } unless block_given? ranges_excl = ensure_exclusive_end([ranges].flatten, range_factory) @top_node = divide_intervals(ranges_excl) end @@ -12,16 +12,16 @@ def initialize(ranges, &range_factory) def divide_intervals(intervals) return nil if intervals.empty? + x_center = center(intervals) - s_center = Array.new - s_left = Array.new - s_right = Array.new + s_center = [] + s_left = [] + s_right = [] intervals.each do |k| - case - when k.end.to_r < x_center + if k.end.to_r < x_center s_left << k - when k.begin.to_r > x_center + elsif k.begin.to_r > x_center s_right << k else s_center << k @@ -29,13 +29,13 @@ def divide_intervals(intervals) end s_center.sort_by! { |x| [x.begin, x.end] } - + Node.new(x_center, s_center, divide_intervals(s_left), divide_intervals(s_right)) end # Search by range or point - DEFAULT_OPTIONS = { unique: true } + DEFAULT_OPTIONS = { unique: true }.freeze def search(query, options = {}) options = DEFAULT_OPTIONS.merge(options) @@ -45,9 +45,9 @@ def search(query, options = {}) result = top_node.search(query) options[:unique] ? result.uniq! : result else - result = point_search(self.top_node, query, [], options[:unique]) + result = point_search(top_node, query, [], options[:unique]) end - result + result end def ==(other) @@ -58,10 +58,9 @@ def ==(other) def ensure_exclusive_end(ranges, range_factory) ranges.map do |range| - case - when !range.respond_to?(:exclude_end?) + if !range.respond_to?(:exclude_end?) range - when range.exclude_end? + elsif range.exclude_end? range else range_factory.call(range.begin, range.end) @@ -88,13 +87,14 @@ def point_search(node, point, result, unique) node.s_center.each do |k| break if k.begin > point + result << k if point < k.end end - - if node_left_node && ( point_r < node_x_center ) + + if node_left_node && (point_r < node_x_center) stack << node_left_node - elsif node_right_node && ( point_r >= node_x_center ) + elsif node_right_node && (point_r >= node_x_center) stack << node_right_node end @@ -105,7 +105,7 @@ def point_search(node, point, result, unique) result end end - end # class Tree + end class Node def initialize(x_center, s_center, left_node, right_node) @@ -118,9 +118,9 @@ def initialize(x_center, s_center, left_node, right_node) def ==(other) x_center == other.x_center && - s_center == other.s_center && - left_node == other.left_node && - right_node == other.right_node + s_center == other.s_center && + left_node == other.left_node && + right_node == other.right_node end # Search by range only @@ -149,26 +149,26 @@ def search_s_center(query) next unless ( # k is entirely contained within the query - (k_begin_gte_q_begin) && - (k_end_lte_q_end) + k_begin_gte_q_begin && + k_end_lte_q_end ) || ( # k's start overlaps with the query - (k_begin_gte_q_begin) && + k_begin_gte_q_begin && (k_begin < query_end) ) || ( # k's end overlaps with the query (k_end > query_begin) && - (k_end_lte_q_end) + k_end_lte_q_end ) || ( # k is bigger than the query (k_begin < query_begin) && (k_end > query_end) ) + result << k end result end - end # class Node - -end # module IntervalTree + end +end From bb60728beda1bcfaa91825c0d8dc69a66748b521 Mon Sep 17 00:00:00 2001 From: Daniel Petri Rocha Date: Sat, 18 Sep 2021 21:59:48 +0200 Subject: [PATCH 08/12] Sort parameter in README.md --- README.md | 9 +++++---- lib/interval_tree.rb | 4 ++-- spec/interval_tree_spec.rb | 18 ++++++++++++++++++ 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 285cec4..a1d4811 100644 --- a/README.md +++ b/README.md @@ -44,11 +44,12 @@ See spec/interval_tree_spec.rb for details. ```ruby require "interval_tree" -itv = [(0...3), (1...4), (3...5), (0...3)] +itv = [(0...3), (1...4), (2...10), (3...5), (0...3)] t = IntervalTree::Tree.new(itv) -p t.search(2) #=> [0...3, 1...4] -p t.search(2, unique: false) #=> [0...3, 0...3, 1...4] -p t.search(1...4) #=> [0...3, 1...4, 3...5] +p t.search(2) #=> [0...3, 1...4, 2...10] +p t.search(2, unique: false) #=> [0...3, 0...3, 1...4, 2...10] +p t.search(2, unique: false, sort: false) #=> [2...10, 0...3, 0...3, 1...4] +p t.search(1...4) #=> [0...3, 1...4, 2...10, 3...5] ``` ## Note diff --git a/lib/interval_tree.rb b/lib/interval_tree.rb index 78bc9ab..a7bfee9 100755 --- a/lib/interval_tree.rb +++ b/lib/interval_tree.rb @@ -35,7 +35,7 @@ def divide_intervals(intervals) end # Search by range or point - DEFAULT_OPTIONS = { unique: true }.freeze + DEFAULT_OPTIONS = { unique: true, sort: true }.freeze def search(query, options = {}) options = DEFAULT_OPTIONS.merge(options) @@ -47,7 +47,7 @@ def search(query, options = {}) else result = point_search(top_node, query, [], options[:unique]) end - result + options[:sort] ? result.sort_by { |x| [x.begin, x.end] } : result end def ==(other) diff --git a/spec/interval_tree_spec.rb b/spec/interval_tree_spec.rb index 5d66da3..e9540bd 100644 --- a/spec/interval_tree_spec.rb +++ b/spec/interval_tree_spec.rb @@ -247,6 +247,24 @@ def initialize(l, r, value = nil) end end + context 'given [(0...30),(20...100)] and a sorted point query "25"' do + it 'returns [(0...30),(20...100)]' do + itvs = [(0...30),(20...100)] + + results = IntervalTree::Tree.new(itvs).search(25, unique: false, sort: true) + expect(results).to match_array([(0...30),(20...100)]) + end + end + + context 'given [(0...30),(20...100)] and an unsorted point query "25"' do + it 'returns [(20...100),(0...30)]' do + itvs = [(0...30),(20...100)] + + results = IntervalTree::Tree.new(itvs).search(25, unique: false, sort: false) + expect(results).to match_array([(20...100), (0...30)]) + 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)] From c4e69c91b31ef6f2494dbb2a37bec3a0ad3e7c5d Mon Sep 17 00:00:00 2001 From: Daniel Petri Rocha <33319791+danielpetri1@users.noreply.github.com> Date: Thu, 2 Dec 2021 17:35:44 +0100 Subject: [PATCH 09/12] Faster queries by considering maximum interval end + Fewer comparisons --- lib/interval_tree.rb | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/lib/interval_tree.rb b/lib/interval_tree.rb index a7bfee9..ab2ea15 100755 --- a/lib/interval_tree.rb +++ b/lib/interval_tree.rb @@ -6,6 +6,7 @@ class Tree def initialize(ranges, &range_factory) range_factory = ->(l, r) { (l...r + 1) } unless block_given? ranges_excl = ensure_exclusive_end([ranges].flatten, range_factory) + ranges_excl.sort_by! { |x| [x.begin, x.end] } @top_node = divide_intervals(ranges_excl) end attr_reader :top_node @@ -77,24 +78,26 @@ def center(intervals) def point_search(node, point, result, unique) stack = [node] - point_r = point.to_r until stack.empty? node = stack.pop node_left_node = node.left_node node_right_node = node.right_node node_x_center = node.x_center + traverse_left = (point < node_x_center) - node.s_center.each do |k| - break if k.begin > point + if point < node.s_center_end + node.s_center.each do |k| + break if k.begin > point - result << k if point < k.end + result << k if point < k.end + end end - if node_left_node && (point_r < node_x_center) + if node_left_node && traverse_left stack << node_left_node - elsif node_right_node && (point_r >= node_x_center) + elsif node_right_node && !traverse_left stack << node_right_node end @@ -111,10 +114,11 @@ class Node def initialize(x_center, s_center, left_node, right_node) @x_center = x_center @s_center = s_center + @s_center_end = s_center.map(&:end).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_center_end, :left_node, :right_node def ==(other) x_center == other.x_center && @@ -126,8 +130,8 @@ def ==(other) # Search by range only def search(query) search_s_center(query) + - (left_node && query.begin.to_r < x_center && left_node.search(query) || []) + - (right_node && query.end.to_r > x_center && right_node.search(query) || []) + (left_node && query.begin < x_center && left_node.search(query) || []) + + (right_node && query.end > x_center && right_node.search(query) || []) end private From 8a4489fa9389ad9defc5a5d4d333db16acff0f75 Mon Sep 17 00:00:00 2001 From: Daniel Petri Rocha <33319791+danielpetri1@users.noreply.github.com> Date: Thu, 2 Dec 2021 17:38:00 +0100 Subject: [PATCH 10/12] Correct expected value using average --- spec/interval_tree_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/interval_tree_spec.rb b/spec/interval_tree_spec.rb index e9540bd..3f6f0fd 100644 --- a/spec/interval_tree_spec.rb +++ b/spec/interval_tree_spec.rb @@ -68,7 +68,7 @@ end context 'given [(1...5), (2...6)]' do - it 'returns 3' do + it 'returns 3.5' do itvs = [(1...5), (2...6),] t = IntervalTree::Tree.new([]) expect(t.__send__(:center, itvs)).to be == 3.5 @@ -425,4 +425,4 @@ def initialize(l, r, value = nil) end end end -end \ No newline at end of file +end From 1b5d92f9f8e0dd6b7fb6628874e2bc5170e2d349 Mon Sep 17 00:00:00 2001 From: Daniel Petri Rocha <33319791+danielpetri1@users.noreply.github.com> Date: Tue, 11 Jan 2022 14:23:19 +0100 Subject: [PATCH 11/12] Edge case patch when s_center_end nil --- lib/interval_tree.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/interval_tree.rb b/lib/interval_tree.rb index ab2ea15..71a1d50 100755 --- a/lib/interval_tree.rb +++ b/lib/interval_tree.rb @@ -84,9 +84,10 @@ def point_search(node, point, result, unique) node_left_node = node.left_node node_right_node = node.right_node node_x_center = node.x_center + node_s_center_end = node.s_center_end traverse_left = (point < node_x_center) - if point < node.s_center_end + if node_s_center_end && point < node_s_center_end node.s_center.each do |k| break if k.begin > point From 04b790188398385e936598f6cdd650c0652209e2 Mon Sep 17 00:00:00 2001 From: Daniel Petri Rocha <33319791+danielpetri1@users.noreply.github.com> Date: Tue, 11 Jan 2022 14:25:05 +0100 Subject: [PATCH 12/12] LICENSE --- lib/interval_tree.rb | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/lib/interval_tree.rb b/lib/interval_tree.rb index 71a1d50..f50d3eb 100755 --- a/lib/interval_tree.rb +++ b/lib/interval_tree.rb @@ -1,6 +1,27 @@ #!/usr/bin/env ruby # frozen_string_literal: true +# Copyright (c) 2011-2021 MISHIMA, Hiroyuki; Simeon Simeonov; Carlos Alonso; Sam Davies; amarzot-yesware; Daniel Petri Rocha + +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + module IntervalTree class Tree def initialize(ranges, &range_factory)