range_hash 2.0.0

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.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in rangehash.gemspec
4
+ gemspec
data/README.md ADDED
@@ -0,0 +1,62 @@
1
+
2
+ range_hash
3
+ ========================
4
+
5
+ ##Problem
6
+
7
+ Given a list of ranges, how do you effectively find if an element exist in any of them?
8
+
9
+ ##Solution
10
+
11
+ [sudo] gem install range_hash
12
+
13
+ ##Example
14
+
15
+ ### Simple
16
+
17
+ require 'range_hash'
18
+ ranges = RangeHash.new
19
+ ranges[1..3] = 'a'
20
+ ranges[10..15] = 'b'
21
+ ranges[27...30] = 'c'
22
+ ranges[7..10] = 'd'
23
+
24
+ ranges[2] #=> 'a'
25
+ ranges[15] #=> 'b'
26
+ ranges[100] #=> nil
27
+
28
+ ### Callbacks
29
+ require 'range_hash'
30
+ ranges = RangeHash.new
31
+ ranges.add_callback(1..13) do |elem|
32
+ puts "#{elem} is between 1 and 3"
33
+ end
34
+ ranges.add_callback(10..15) do |elem|
35
+ puts "#{elem} is between 10 and 15!"
36
+ end
37
+ ranges.add_callback(27...30) do |elem|
38
+ puts "#{elem} is between 27 and 29!"
39
+ end
40
+ ranges.add_callback(7..10) do |elem|
41
+ puts "#{elem} is between 7 and 10!"
42
+ end
43
+ ranges.add_callback(:default) do |elem|
44
+ puts "Couldn't find a range for #{elem}"
45
+ end
46
+
47
+ ranges.call(2) #=> 2 is between 1 and 3!
48
+ ranges.call(15) #=> 15 is between 10 and 15!
49
+ ranges.call(100) #=> Couldn't find a range of 100
50
+
51
+
52
+
53
+ ##Wait ... why?
54
+
55
+ Because O(log n) is better than O(N). RangeHash keeps an internal sorted array that uses a binary search to find your range. This allows you to achieve (log n) search. Another option would be to store a dense hash that stores all possible elements within the ranges, however this isn't efficient in terms of memory usage. This is great happy medium.
56
+
57
+ On-top of that, RangeHash provides some awesome functionality such as callbacks.
58
+
59
+
60
+ ##Notes
61
+
62
+ Currently does not support ranges that overlap one another.
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require 'bundler'
2
+ include Rake::DSL
3
+ Bundler::GemHelper.install_tasks
4
+
5
+ require 'rspec/core/rake_task'
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task :default => :spec
9
+ task :test => :spec
10
+
@@ -0,0 +1,3 @@
1
+ class RangeHash
2
+ VERSION = "2.0.0"
3
+ end
data/lib/range_hash.rb ADDED
@@ -0,0 +1,101 @@
1
+ require "rangehash/version"
2
+ require 'forwardable'
3
+
4
+ class Range
5
+ def real_end
6
+ exclude_end? ? self.end - 1 : self.end
7
+ end
8
+ end
9
+
10
+ class RangeHashElement
11
+ extend Forwardable
12
+
13
+ attr_reader :range
14
+ attr_accessor :value, :callback
15
+
16
+ def_delegators :@range, :begin
17
+ def_delegator :@range, :real_end, :end
18
+ def initialize(opts={})
19
+ @range, @value, @callback = opts[:range], opts[:value], opts[:callback]
20
+ end
21
+ end
22
+
23
+ class RangeHash
24
+ def initialize
25
+ @arr = []
26
+ @default_callback = nil
27
+ end
28
+
29
+ def [](key)
30
+ result = search(key)
31
+ end
32
+
33
+ def call(key)
34
+ index = search(key, true)
35
+ if index < 0
36
+ @default_callback.call(key) if @default_callback.respond_to?(:call)
37
+ else
38
+ callback = @arr[index].callback
39
+ callback.call(key) if callback.respond_to?(:call)
40
+ end
41
+ nil
42
+ end
43
+
44
+ def []=(range,value)
45
+ raise ArgumentError.new("Key should be a range") unless Range === range
46
+ edit_or_create(range, :range => range, :value => value)
47
+ value
48
+ end
49
+
50
+ def add_callback(range, &callback)
51
+ raise ArgumentError.new("Need to pass a callback in the form of a block") unless block_given?
52
+ unless Range === range || range == :default
53
+ raise ArgumentError.new("Argument needs to be a range or :default")
54
+ end
55
+ if range == :default
56
+ @default_callback = callback
57
+ else
58
+ edit_or_create(range, :range => range, :callback => callback)
59
+ end
60
+ nil
61
+ end
62
+
63
+ def to_s
64
+ "#RangeHash<@ranges=[#{@arr.map(&:range).join(",")}]>"
65
+ end
66
+
67
+ private
68
+
69
+ def edit_or_create(range, element_opts={})
70
+ index = search(range.real_end, true)
71
+ if index < 0
72
+ elem = RangeHashElement.new(element_opts)
73
+ index = -(index + 1)
74
+ @arr.insert(index, elem)
75
+ else
76
+ element_opts.delete(:range)
77
+ edit_attr, attr_value = element_opts.to_a.first
78
+ @arr[index].instance_variable_set(:"@#{edit_attr}", attr_value)
79
+ end
80
+ end
81
+
82
+ def search(key, ret_index=false)
83
+ low, high = 0, @arr.size - 1
84
+ mid = 0
85
+
86
+ while low <= high
87
+ mid = (low + high)/ 2
88
+ if key <= @arr[mid].end && key >= @arr[mid].begin
89
+ val = ret_index ? mid : @arr[mid].value
90
+ return val
91
+ elsif key < @arr[mid].end
92
+ high = mid - 1
93
+ else
94
+ low = mid + 1
95
+ end
96
+ end
97
+
98
+ return -low - 1 if ret_index
99
+ end
100
+ end
101
+
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "range_hash/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "range_hash"
7
+ s.version = RangeHash::VERSION
8
+ s.authors = ["Mike Lewis"]
9
+ s.email = ["ft.mikelewis@gmail.com"]
10
+ s.homepage = ""
11
+ s.summary = %q{An efficient hash that allows ranges to be keys and searched given an element within those ranges.}
12
+ s.description = %q{An efficient hash that allows ranges to be keys and searched given an element within those ranges.}
13
+
14
+ s.rubyforge_project = "range_hash"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ # specify any dependencies here; for example:
22
+ s.add_development_dependency "rspec"
23
+ s.add_development_dependency "rake"
24
+ # s.add_runtime_dependency "rest-client"
25
+ end
@@ -0,0 +1,101 @@
1
+ require 'spec_helper'
2
+
3
+ describe RangeHash do
4
+ before do
5
+ @rh = RangeHash.new
6
+ end
7
+ [:[], :[]=].each do |meth|
8
+ it "should respond to #{meth}" do
9
+ @rh.should respond_to(meth)
10
+ end
11
+ end
12
+
13
+ context "integeration" do
14
+ before do
15
+ c = (13..16)
16
+ d = (20...25)
17
+ a = (1..10)
18
+ b = (10...13)
19
+
20
+ @rh[c] = 'c'
21
+ @rh[d] = 'd'
22
+ @rh[a] = 'a'
23
+ @rh[b] = 'b'
24
+ end
25
+ it "should insert to keep the internal array" do
26
+ internal_array = @rh.instance_eval { @arr }
27
+ internal_array.map(&:value).should == ['a', 'b', 'c', 'd']
28
+ end
29
+
30
+ it "[] should return the associated value for that range" do
31
+ @rh[21].should == 'd'
32
+ end
33
+
34
+ it "[] should return nil for a value that is not in a range" do
35
+ @rh[25].should == nil
36
+ end
37
+
38
+ it "should beable to replace an element" do
39
+ @rh[(13..16)] = 'new'
40
+
41
+ @rh[14].should == 'new'
42
+ end
43
+
44
+ it "should accept callbacks and return nil" do
45
+ result = nil
46
+ n = @rh.add_callback(100..105) do |elem|
47
+ result = elem
48
+ end
49
+
50
+ @rh.call(103)
51
+
52
+ result.should == 103
53
+ n.should be_nil
54
+ end
55
+
56
+ it "should accept a default callback" do
57
+ result = nil
58
+ @rh.add_callback(:default) do |elem|
59
+ result = elem
60
+ end
61
+ @rh.call(2000)
62
+ result.should == 2000
63
+ end
64
+
65
+ it "should return nil and not blow up when accessing a callback that does not exist" do
66
+ lambda { @rh.call(3) }.should_not raise_error
67
+ @rh.call(3).should be_nil
68
+ end
69
+
70
+ it "should raise an ArgumentError if no block is passed into add_callback" do
71
+ lambda { @rh.add_callback((1..3)) }.should raise_error(ArgumentError)
72
+ end
73
+
74
+ it "should raise an ArgumentError if a non range or not default is passed into a callback" do
75
+ lambda { @rh.add_callback(3) }.should raise_error(ArgumentError)
76
+ end
77
+
78
+ it "should raise an ArgumentError if key is not range" do
79
+ lambda { @rh["yo"] = 5}.should raise_error(ArgumentError)
80
+ end
81
+
82
+ end
83
+
84
+ end
85
+
86
+ describe RangeHashElement do
87
+ it "should handle inclusive ranges for end" do
88
+ e = RangeHashElement.new(:range => (1..10), :value => 'a')
89
+ e.end.should == 10
90
+ end
91
+
92
+ it "should handle begin for ranges" do
93
+ e = RangeHashElement.new(:range => (1..10), :value => 'a')
94
+ e.begin.should == 1
95
+ end
96
+
97
+ it "should handle exclusive ranges end" do
98
+ e = RangeHashElement.new(:range => (1...10), :value => 'a')
99
+ e.end.should == 9
100
+ end
101
+ end
@@ -0,0 +1,3 @@
1
+ $:.unshift File.dirname(__FILE__) + '/../lib'
2
+ require 'range_hash'
3
+
metadata ADDED
@@ -0,0 +1,81 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: range_hash
3
+ version: !ruby/object:Gem::Version
4
+ version: 2.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Mike Lewis
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-11-26 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: &2151811980 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: *2151811980
25
+ - !ruby/object:Gem::Dependency
26
+ name: rake
27
+ requirement: &2151810580 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *2151810580
36
+ description: An efficient hash that allows ranges to be keys and searched given an
37
+ element within those ranges.
38
+ email:
39
+ - ft.mikelewis@gmail.com
40
+ executables: []
41
+ extensions: []
42
+ extra_rdoc_files: []
43
+ files:
44
+ - .gitignore
45
+ - .rspec
46
+ - Gemfile
47
+ - README.md
48
+ - Rakefile
49
+ - lib/range_hash.rb
50
+ - lib/range_hash/version.rb
51
+ - range_hash.gemspec
52
+ - spec/rangehash_spec.rb
53
+ - spec/spec_helper.rb
54
+ homepage: ''
55
+ licenses: []
56
+ post_install_message:
57
+ rdoc_options: []
58
+ require_paths:
59
+ - lib
60
+ required_ruby_version: !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ required_rubygems_version: !ruby/object:Gem::Requirement
67
+ none: false
68
+ requirements:
69
+ - - ! '>='
70
+ - !ruby/object:Gem::Version
71
+ version: '0'
72
+ requirements: []
73
+ rubyforge_project: range_hash
74
+ rubygems_version: 1.8.10
75
+ signing_key:
76
+ specification_version: 3
77
+ summary: An efficient hash that allows ranges to be keys and searched given an element
78
+ within those ranges.
79
+ test_files:
80
+ - spec/rangehash_spec.rb
81
+ - spec/spec_helper.rb