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 +7 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +96 -0
- data/Rakefile +2 -0
- data/lib/range_tree/version.rb +3 -0
- data/lib/range_tree.rb +107 -0
- data/range_tree.gemspec +23 -0
- metadata +80 -0
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
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
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
|
data/range_tree.gemspec
ADDED
@@ -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:
|