123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200 |
- #!/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)
- 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
- def divide_intervals(intervals)
- return nil if intervals.empty?
- x_center = center(intervals)
- s_center = []
- s_left = []
- s_right = []
- intervals.each do |k|
- if k.end < x_center
- s_left << k
- elsif k.begin > 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, sort: true }.freeze
- def search(query, options = {})
- options = DEFAULT_OPTIONS.merge(options)
- return nil unless @top_node
- if query.respond_to?(:begin)
- result = top_node.search(query)
- options[:unique] ? result.uniq! : result
- else
- result = point_search(top_node, query, [], options[:unique])
- end
- options[:sort] ? result.sort_by { |x| [x.begin, x.end] } : result
- end
- def ==(other)
- top_node == other.top_node
- end
- private
- def ensure_exclusive_end(ranges, range_factory)
- ranges.map do |range|
- if !range.respond_to?(:exclude_end?)
- range
- elsif range.exclude_end?
- range
- else
- range_factory.call(range.begin, range.end)
- end
- end
- end
- def center(intervals)
- (
- intervals.map(&:begin).min +
- intervals.map(&:end).max
- ) / 2
- end
- def point_search(node, point, result, unique)
- stack = [node]
- 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_end = node.s_center_end
- traverse_left = (point < node_x_center)
- if node_s_center_end && point < node_s_center_end
- node.s_center.each do |k|
- break if k.begin > point
- result << k if point < k.end
- end
- end
- if node_left_node && traverse_left
- stack << node_left_node
- elsif node_right_node && !traverse_left
- stack << node_right_node
- end
- end
- if unique
- result.uniq
- else
- result
- end
- end
- end
- 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, :s_center_end, :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
- end
- # Search by range only
- def search(query)
- search_s_center(query) +
- (left_node && query.begin < x_center && left_node.search(query) || []) +
- (right_node && query.end > x_center && right_node.search(query) || [])
- end
- private
- def search_s_center(query)
- result = []
- s_center.each do |k|
- 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_gte_q_begin &&
- k_end_lte_q_end
- ) || (
- # k's start overlaps with the query
- 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 is bigger than the query
- (k_begin < query_begin) &&
- (k_end > query_end)
- )
- result << k
- end
- result
- end
- end
- end
|