range_tree 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 8d1823bf3fb8b7ad048e704508d6a753a05a8456
4
+ data.tar.gz: 707cff2d18006ca2991a63622641b84274311474
5
+ SHA512:
6
+ metadata.gz: ebb2ce6159ca59749afd5242d7b0fc0106a12fa95f6d2a662e9406dccdf091ef176c2d65d123a90576ced40906b997d672795d3e6b1eca83b393ca6dc761efe4
7
+ data.tar.gz: afa957ed7156481bed9bb21d8f44c5bb6a08accd74d90214612e4bbd2945a603f5728b8528923aaf634ec53e04f819291444100ee82253b14c2d68d393bc49eb
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in range_tree.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Wouter Coppieters
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,96 @@
1
+ # RangeTree
2
+
3
+ Range Tree provides a flexible and efficient data structure for easily querying variables indexed by ranges.
4
+
5
+ With a range tree you can easily query state at a given point
6
+ or timestamp and or all variables that overlap with a given range.
7
+
8
+ You can also use it to iterate through all points or all unique configurations contained within the range tree.
9
+
10
+ You can use integers, floats, dates, times or similar comparable
11
+ objects as range keys.
12
+
13
+ For small trees you can use the default settings
14
+ which rebuild the tree every time you add a value.
15
+
16
+ If you need to build a very large tree with 1000's of values
17
+ you need to pass ```rebuild_on_add: false``` to the tree.
18
+ Then add everything to your tree and only once it is complete
19
+ you must call ```tree.rebuild``` to build your index.
20
+
21
+ ## Installation
22
+
23
+ Add this line to your application's Gemfile:
24
+
25
+ ```ruby
26
+ gem 'range_tree'
27
+ ```
28
+
29
+ And then execute:
30
+
31
+ $ bundle
32
+
33
+ Or install it yourself as:
34
+
35
+ $ gem install range_tree
36
+
37
+ ## Usage
38
+
39
+ # Below is an example using integers (formatted as dates) as keys. You can just as easily use dates, times
40
+ # floats or other comparable types.
41
+
42
+ require 'range_tree'
43
+
44
+ staff_holidays = RangeTree::Tree.new
45
+
46
+ staff_holidays[2000_01_01, 2000_01_15] = "John"
47
+ staff_holidays[2000_01_15, 2000_01_31] = "Greig"
48
+ staff_holidays[2000_01_20, 2000_02_05] = "Eve"
49
+ staff_holidays[2000_01_03, 2000_01_08] = "Alice"
50
+ staff_holidays[2000_01_18, 2000_01_20] = "Bob"
51
+
52
+ # Check who is on holiday on certain dates
53
+ staff_holidays[2000_01_04]
54
+ => ["Alice", "John"]
55
+
56
+ # Check all staff members on holiday within a certain period by passing a range
57
+ staff_holidays[2000_01_12..2011_01_19]
58
+ => ["John", "Bob", "Greig", "Eve"]
59
+
60
+
61
+ # Iterate through all periods where the system state changes
62
+ staff_holidays.distinct.each do |date, staff|
63
+ puts "From #{date} #{staff} will be on holiday"
64
+ end
65
+
66
+ =>
67
+ From 20000101 ["John"] will be on holiday
68
+ From 20000103 ["John", "Alice"] will be on holiday
69
+ From 20000109 ["John"] will be on holiday
70
+ From 20000115 ["John", "Greig"] will be on holiday
71
+ From 20000116 ["Greig"] will be on holiday
72
+ From 20000118 ["Greig", "Bob"] will be on holiday
73
+ From 20000120 ["Greig", "Bob", "Eve"] will be on holiday
74
+ From 20000121 ["Greig", "Eve"] will be on holiday
75
+ From 20000132 ["Eve"] will be on holiday
76
+ From 20000206 [] will be on holiday
77
+
78
+ # Iterate though all periods in the tree.
79
+ staff_holidays.all.each do |date, staff|
80
+ puts "#{staff} is on holiday on #{date}"
81
+ end
82
+
83
+ =>
84
+ ["John"] is on holiday on 20000101
85
+ ["John"] is on holiday on 20000102
86
+ ["John", "Alice"] is on holiday on 20000103
87
+ ["John", "Alice"] is on holiday on 20000104
88
+ ["John", "Alice"] is on holiday on 20000105
89
+ ["John", "Alice"] is on holiday on 20000106
90
+ ["John", "Alice"] is on holiday on 20000107
91
+ ["John", "Alice"] is on holiday on 20000108
92
+ ["John"] is on holiday on 20000109
93
+ ["John"] is on holiday on 20000110
94
+ ["John"] is on holiday on 20000111
95
+ ...
96
+
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -0,0 +1,3 @@
1
+ module RangeTree
2
+ VERSION = "0.0.1"
3
+ end
data/lib/range_tree.rb ADDED
@@ -0,0 +1,107 @@
1
+ require "range_tree/version"
2
+
3
+ module RangeTree
4
+ class Tree
5
+ require 'set'
6
+ attr_accessor :children, :root, :starts, :ends, :rebuild_on_add
7
+
8
+ MAX_REBUILD_SIZE = 500
9
+
10
+ def initialize(rebuild_on_add: true)
11
+ self.children = []
12
+ self.rebuild_on_add = rebuild_on_add
13
+ self.starts = Hash.new{|h,k| h[k] = []}
14
+ self.ends = Hash.new{|h,k| h[k] = []}
15
+ end
16
+
17
+ def add(min, max, value)
18
+ node = Node.new(min, max, value)
19
+ self.children << node
20
+ self.starts[min] << node
21
+ self.ends[max.respond_to?(:succ) ? max.succ : max + 1] << node
22
+ self.rebuild if self.rebuild_on_add
23
+ end
24
+
25
+ def []=(min, max, value)
26
+ add(min, max, value)
27
+ end
28
+
29
+ def >>(value)
30
+ remove(value)
31
+ end
32
+
33
+ def remove(value)
34
+ return unless node_to_remove = self.children.find{|child| child.value == value}
35
+ self.children.delete(node_to_remove)
36
+ self.starts[node_to_remove.min].delete(node_to_remove)
37
+ self.ends[node_to_remove.max].delete(node_to_remove)
38
+ self.rebuild if self.rebuild_on_add
39
+ return node_to_remove.min..node_to_remove.max
40
+ end
41
+
42
+ def rebuild
43
+ current_level = self.children.sort_by(&:mid_point)
44
+ current_level = current_level.each_slice(2).map do |pair|
45
+ Node.new(pair.map(&:min).min, pair.map(&:max).max, pair)
46
+ end while current_level.length > 1
47
+ self.root = current_level.first
48
+ end
49
+
50
+
51
+ def distinct &block
52
+ iterate([*self.starts.keys, *self.ends.keys].uniq.sort).each(&block)
53
+ end
54
+
55
+ def all &block
56
+ iterate(self.starts.keys.min..self.ends.keys.max).each(&block)
57
+ end
58
+
59
+ def [](index)
60
+ return nil unless self.root
61
+ case index
62
+ when Range
63
+ self.root.search_range(index).map(&:value)
64
+ when Fixnum
65
+ self.root.search(index).map(&:value)
66
+ else
67
+ raise "Unexpected index type #{index.class}"
68
+ end
69
+ end
70
+
71
+ private
72
+ def iterate(range)
73
+ return [] unless self.root
74
+ collection = Set.new(self.root.search(range.first))
75
+ Enumerator.new do |enum|
76
+ range.each do |current_point|
77
+ self.ends[current_point].each{|node| collection.delete(node)} if self.ends.include?(current_point)
78
+ self.starts[current_point].each{|node| collection << node } if self.starts.include?(current_point)
79
+ enum.yield current_point, collection.map(&:value)
80
+ end
81
+ end
82
+ end
83
+
84
+ end
85
+
86
+ class Node
87
+ attr_accessor :min, :max, :value, :mid_point
88
+ def initialize(min, max, value)
89
+ self.min, self.max, self.value = min, max, value
90
+ self.mid_point = self.min + (self.max - self.min) / 2.0
91
+ end
92
+
93
+ def search(index)
94
+ return [] unless (min..max) === index
95
+ Array === value && value.map{|v| v.search(index)}.flatten || [self]
96
+ end
97
+
98
+ def search_range(range)
99
+ return [] unless intersect((self.min..self.max),range)
100
+ Array === value && value.map{|v| v.search_range(range)}.flatten || [self]
101
+ end
102
+
103
+ def intersect(r1, r2)
104
+ !(r2.end < r1.first || r2.first > r1.end)
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,23 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'range_tree/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "range_tree"
8
+ spec.version = RangeTree::VERSION
9
+ spec.authors = ["Wouter Coppieters"]
10
+ spec.email = ["wouter.coppieters@youdo.co.nz"]
11
+ spec.summary = %q{An efficient tree structure for querying on data with overlapping ranges}
12
+ spec.description = %q{An efficient tree structure for querying on data with overlapping ranges}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.7"
22
+ spec.add_development_dependency "rake", "~> 10.0"
23
+ end
metadata ADDED
@@ -0,0 +1,80 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: range_tree
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Wouter Coppieters
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-07-30 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.7'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.7'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ description: An efficient tree structure for querying on data with overlapping ranges
42
+ email:
43
+ - wouter.coppieters@youdo.co.nz
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - Gemfile
49
+ - LICENSE.txt
50
+ - README.md
51
+ - Rakefile
52
+ - lib/range_tree.rb
53
+ - lib/range_tree/version.rb
54
+ - range_tree.gemspec
55
+ homepage: ''
56
+ licenses:
57
+ - MIT
58
+ metadata: {}
59
+ post_install_message:
60
+ rdoc_options: []
61
+ require_paths:
62
+ - lib
63
+ required_ruby_version: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ required_rubygems_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: '0'
73
+ requirements: []
74
+ rubyforge_project:
75
+ rubygems_version: 2.2.2
76
+ signing_key:
77
+ specification_version: 4
78
+ summary: An efficient tree structure for querying on data with overlapping ranges
79
+ test_files: []
80
+ has_rdoc: