deep_tree 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/Gemfile +8 -0
- data/LICENSE.txt +22 -0
- data/README.md +84 -0
- data/Rakefile +6 -0
- data/benchmarks/bad_path.rb +22 -0
- data/benchmarks/benchmark_helper.rb +20 -0
- data/benchmarks/good_path.rb +22 -0
- data/deep_tree.gemspec +22 -0
- data/lib/deep_tree.rb +1 -0
- data/lib/deep_tree/deep_tree.rb +41 -0
- data/lib/deep_tree/hash.rb +11 -0
- data/lib/deep_tree/version.rb +3 -0
- data/spec/deep_tree_spec.rb +60 -0
- data/spec/hash_spec.rb +48 -0
- data/spec/spec_helper.rb +5 -0
- metadata +77 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 28279e6c4a07b1a3884589b8f9129676c542ed70
|
4
|
+
data.tar.gz: 390b491664e74b1b1c071e66e41a1bb49f7dc169
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 4dc3c17bd5e7655e65f9a3b4e91f6696b1c702398215db52b6aeff98fd230620db98299309aaadc7a584317edad32ef77e2b766f03948c4a158c1b1d167912e1
|
7
|
+
data.tar.gz: 99157c2776fb9b21ffd341986471c4559ec36cdf332d03e4e5df80c65ace140836877dab38b9ed13fd14d76479d35fecdea4b9b4ba276beeba81ff4de4d2bc8d
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Patrick Tulskie
|
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,84 @@
|
|
1
|
+
# DeepTree
|
2
|
+
|
3
|
+
DeepTree is a simple class that loops through nodes in nested hashes to find a node that you're looking for. It's built to alleviate this:
|
4
|
+
|
5
|
+
tons_of_hashes['first_node']['second_node']['third_node'] rescue nil
|
6
|
+
|
7
|
+
Not only does this mask other issues in your code, but it is also super slow especially if you're digging through the same tree structure several times in a given app.
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
Add this line to your application's Gemfile:
|
12
|
+
|
13
|
+
gem 'deep_tree'
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
$ bundle
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
$ gem install deep_tree
|
22
|
+
|
23
|
+
## Usage
|
24
|
+
|
25
|
+
There are three ways to use DeepTree depending on your needs. The first way is to simply use an instance:
|
26
|
+
|
27
|
+
tree = DeepTree.new(tons_of_hashes)
|
28
|
+
tree.get_leaf('first_node', 'second_node', 'third_node')
|
29
|
+
|
30
|
+
Another way to use it is by extending the `Hash` class.
|
31
|
+
|
32
|
+
require 'deep_tree/hash'
|
33
|
+
tons_of_hashes.get_leaf('first_node', 'second_node', 'third_node')
|
34
|
+
|
35
|
+
Many people try not to reopen Ruby's base classes. If you want this functionality on the `Hash` class then you can load it up. If you don't then you don't have to.
|
36
|
+
|
37
|
+
Finally, you can just use the class method like this:
|
38
|
+
|
39
|
+
DeepTree.get_leaf(tons_of_hashes, 'first_node', 'second_node')
|
40
|
+
|
41
|
+
Additionally, `DeepTree::get_leaf` supports evaluating a block if the result is `nil`.
|
42
|
+
|
43
|
+
DeepTree.get_leaf(tons_of_hashes, 'first_node', 'second_node') do
|
44
|
+
logger.info('That leaf couldn't be found.')
|
45
|
+
nil
|
46
|
+
end
|
47
|
+
|
48
|
+
Both the instance and hash extension all use `DeepTree::get_leaf` so they support passing a block as well.
|
49
|
+
|
50
|
+
## A Caveat
|
51
|
+
|
52
|
+
All nodes except for the final leaf must be hashes in order for this to work. DeepTree will return nil for anything else.
|
53
|
+
|
54
|
+
## Benchmarks
|
55
|
+
|
56
|
+
These test were run on a MacBook Pro Retina 2.7ghz w/ 16 GB RAM. Each test was run 200k times. Benchmark files are in the benchmarks directory.
|
57
|
+
|
58
|
+
Good Path
|
59
|
+
user system total real
|
60
|
+
DeepTree::get_leaf 0.260000 0.110000 0.370000 ( 0.375624)
|
61
|
+
Hash#get_leaf 0.500000 0.130000 0.630000 ( 0.622437)
|
62
|
+
DeepTree#get_leaf 0.600000 0.030000 0.630000 ( 0.630710)
|
63
|
+
Hash[] w/ rescue 0.080000 0.010000 0.090000 ( 0.094365)
|
64
|
+
Hashie::Mash 4.390000 0.240000 4.630000 ( 4.617386)
|
65
|
+
|
66
|
+
Bad Path
|
67
|
+
user system total real
|
68
|
+
DeepTree::get_leaf 0.210000 0.120000 0.330000 ( 0.328618)
|
69
|
+
Hash#get_leaf 0.530000 0.150000 0.680000 ( 0.679037)
|
70
|
+
DeepTree#get_leaf 0.620000 0.030000 0.650000 ( 0.646792)
|
71
|
+
Hash[] w/ rescue 0.510000 0.050000 0.560000 ( 0.567455)
|
72
|
+
Hashie::Mash 6.260000 0.400000 6.660000 ( 6.744009)
|
73
|
+
|
74
|
+
So, in the case where the whole path is good, simply chaining together hash calls is the clear winner (but if that was always the case, you wouldn't be here right now). In the event that the path is broken somewhere along the line, DeepTree's solution is at least 2x faster for this extremely contrived example. Benchmarks are in the benchmarks directory for your viewing pleasure.
|
75
|
+
|
76
|
+
I recently added Hashie::Mash to the mix here since everyone I showed this to told me that I should try Hashie::Mash. I have had lots of performance and memory issues with Hashie::Mash in the past and as of the latest version (2.0.5) it doesn't seem to be any better.
|
77
|
+
|
78
|
+
## Contributing
|
79
|
+
|
80
|
+
1. Fork it
|
81
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
82
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
83
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
84
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require_relative 'benchmark_helper'
|
2
|
+
|
3
|
+
bad_path = %w(three_level nope nada)
|
4
|
+
|
5
|
+
puts "Bad Path"
|
6
|
+
Benchmark.bm do |x|
|
7
|
+
x.report('DeepTree::get_leaf') do
|
8
|
+
$iterations.times { DeepTree.get_leaf($hash, *bad_path) }
|
9
|
+
end
|
10
|
+
x.report('Hash#get_leaf ') do
|
11
|
+
$iterations.times { $hash.get_leaf($hash, *bad_path) }
|
12
|
+
end
|
13
|
+
x.report('DeepTree#get_leaf ') do
|
14
|
+
$iterations.times { tree = DeepTree.new($hash); tree.get_leaf($hash, *bad_path) }
|
15
|
+
end
|
16
|
+
x.report('Hash[] w/ rescue ') do
|
17
|
+
$iterations.times { $hash['three_level']['nope']['nada'] rescue nil }
|
18
|
+
end
|
19
|
+
x.report('Hashie::Mash ') do
|
20
|
+
$iterations.times { Hashie::Mash.new($hash).three_level!.nope!.nada }
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
require 'benchmark'
|
4
|
+
require_relative '../lib/deep_tree'
|
5
|
+
require_relative '../lib/deep_tree/hash'
|
6
|
+
|
7
|
+
Bundler.require :benchmarks
|
8
|
+
|
9
|
+
$iterations = 200_000
|
10
|
+
|
11
|
+
$hash = {
|
12
|
+
'one_level' => 'SUCCESS',
|
13
|
+
'three_level' => {
|
14
|
+
'level_two' => {
|
15
|
+
'level_one' => 'HOORAY'
|
16
|
+
}
|
17
|
+
}
|
18
|
+
}
|
19
|
+
|
20
|
+
GC.disable
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require_relative 'benchmark_helper'
|
2
|
+
|
3
|
+
good_path = %w(three_level level_two level_one)
|
4
|
+
|
5
|
+
puts "Good Path"
|
6
|
+
Benchmark.bm do |x|
|
7
|
+
x.report('DeepTree::get_leaf') do
|
8
|
+
$iterations.times { DeepTree.get_leaf($hash, *good_path) }
|
9
|
+
end
|
10
|
+
x.report('Hash#get_leaf ') do
|
11
|
+
$iterations.times { $hash.get_leaf($hash, *good_path) }
|
12
|
+
end
|
13
|
+
x.report('DeepTree#get_leaf ') do
|
14
|
+
$iterations.times { tree = DeepTree.new($hash); tree.get_leaf($hash, *good_path) }
|
15
|
+
end
|
16
|
+
x.report('Hash[] w/ rescue ') do
|
17
|
+
$iterations.times { $hash['three_level']['level_two']['level_one'] rescue nil }
|
18
|
+
end
|
19
|
+
x.report('Hashie::Mash ') do
|
20
|
+
$iterations.times { Hashie::Mash.new($hash).three_level.level_two.level_one }
|
21
|
+
end
|
22
|
+
end
|
data/deep_tree.gemspec
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'deep_tree/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "deep_tree"
|
8
|
+
gem.version = DeepTree::VERSION
|
9
|
+
gem.authors = ["Patrick Tulskie"]
|
10
|
+
gem.email = ["patricktulskie@gmail.com"]
|
11
|
+
gem.description = %q{DeepTree simplifies fetching deeply nested nodes in Ruby hashes.}
|
12
|
+
gem.summary = %q{Aids with finding deeply nested nodes in Ruby hashes.}
|
13
|
+
gem.homepage = "https://github.com/patricktulskie/deep_tree"
|
14
|
+
gem.licenses = %w(MIT)
|
15
|
+
|
16
|
+
gem.files = `git ls-files`.split($/)
|
17
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
18
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
19
|
+
gem.require_paths = ["lib"]
|
20
|
+
|
21
|
+
gem.add_development_dependency('minitest')
|
22
|
+
end
|
data/lib/deep_tree.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require_relative 'deep_tree/deep_tree'
|
@@ -0,0 +1,41 @@
|
|
1
|
+
class DeepTree
|
2
|
+
|
3
|
+
Error = Class.new(StandardError)
|
4
|
+
InvalidTreeError = Class.new(Error)
|
5
|
+
|
6
|
+
attr_accessor :tree
|
7
|
+
|
8
|
+
def self.get_leaf(parent, *args)
|
9
|
+
args.each_with_index do |key, index|
|
10
|
+
result = case
|
11
|
+
when index == (args.length - 1)
|
12
|
+
return parent[key].nil? && block_given? ? yield : parent[key]
|
13
|
+
when parent[key].is_a?(Hash)
|
14
|
+
parent = parent[key]
|
15
|
+
else
|
16
|
+
return block_given? ? yield : nil
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize(tree)
|
22
|
+
if tree.kind_of?(Hash)
|
23
|
+
self.tree = tree
|
24
|
+
else
|
25
|
+
raise InvalidTreeError, "Expected a kind of Hash but got an instance of #{tree.class}"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def get_leaf(*args)
|
30
|
+
if block_given?
|
31
|
+
DeepTree.get_leaf(tree, *args) { yield }
|
32
|
+
else
|
33
|
+
DeepTree.get_leaf(tree, *args)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def method_missing(*args)
|
38
|
+
self.tree(args)
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require_relative "spec_helper"
|
2
|
+
|
3
|
+
describe DeepTree do
|
4
|
+
|
5
|
+
before do
|
6
|
+
@result_data = {
|
7
|
+
'one_level' => 'SUCCESS',
|
8
|
+
'three_level' => {
|
9
|
+
'level_two' => {
|
10
|
+
'level_one' => 'HOORAY'
|
11
|
+
}
|
12
|
+
}
|
13
|
+
}
|
14
|
+
end
|
15
|
+
|
16
|
+
describe :class do
|
17
|
+
|
18
|
+
it "raises an exception for invalid inputs" do
|
19
|
+
proc { DeepTree.new(Array.new) }.must_raise(DeepTree::InvalidTreeError)
|
20
|
+
end
|
21
|
+
|
22
|
+
it "gets a leaf node" do
|
23
|
+
DeepTree.get_leaf(@result_data, 'three_level', 'level_two', 'level_one').must_equal 'HOORAY'
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
describe :instance do
|
29
|
+
|
30
|
+
before(:each) do
|
31
|
+
@deep_tree = DeepTree.new(@result_data)
|
32
|
+
end
|
33
|
+
|
34
|
+
it "gets a single level's node" do
|
35
|
+
@deep_tree.get_leaf('one_level').must_equal 'SUCCESS'
|
36
|
+
end
|
37
|
+
|
38
|
+
it "gets a deeply nested node" do
|
39
|
+
@deep_tree.get_leaf('three_level', 'level_two', 'level_one').must_equal 'HOORAY'
|
40
|
+
end
|
41
|
+
|
42
|
+
it "responds with nil for a shallow missing node" do
|
43
|
+
@deep_tree.get_leaf('missing').must_be_nil
|
44
|
+
end
|
45
|
+
|
46
|
+
it "responds with nil for a deep missing node" do
|
47
|
+
@deep_tree.get_leaf('three_level', 'level_two', 'missing').must_be_nil
|
48
|
+
end
|
49
|
+
|
50
|
+
it "responds with nil when it encounters a non-hash in the path" do
|
51
|
+
@deep_tree.get_leaf('three_level', 'level_two', 'level_one', 'level_zero').must_be_nil
|
52
|
+
end
|
53
|
+
|
54
|
+
it "evaluates a block instead of returning nil if a block is given" do
|
55
|
+
@deep_tree.get_leaf('nope') { 'potato' }.must_equal 'potato'
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
data/spec/hash_spec.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
require_relative "spec_helper"
|
2
|
+
|
3
|
+
describe Hash do
|
4
|
+
|
5
|
+
describe :instance do
|
6
|
+
|
7
|
+
before(:each) do
|
8
|
+
@result_data = {
|
9
|
+
'one_level' => 'SUCCESS',
|
10
|
+
'three_level' => {
|
11
|
+
'level_two' => {
|
12
|
+
'level_one' => 'HOORAY'
|
13
|
+
}
|
14
|
+
}
|
15
|
+
}
|
16
|
+
end
|
17
|
+
|
18
|
+
it "responds to get_leaf" do
|
19
|
+
@result_data.respond_to?(:get_leaf).must_equal true
|
20
|
+
end
|
21
|
+
|
22
|
+
it "gets a single level's node" do
|
23
|
+
@result_data.get_leaf('one_level').must_equal 'SUCCESS'
|
24
|
+
end
|
25
|
+
|
26
|
+
it "gets a deeply nested node" do
|
27
|
+
@result_data.get_leaf('three_level', 'level_two', 'level_one').must_equal 'HOORAY'
|
28
|
+
end
|
29
|
+
|
30
|
+
it "responds with nil for a shallow missing node" do
|
31
|
+
@result_data.get_leaf('missing').must_be_nil
|
32
|
+
end
|
33
|
+
|
34
|
+
it "responds with nil for a deep missing node" do
|
35
|
+
@result_data.get_leaf('three_level', 'level_two', 'missing').must_be_nil
|
36
|
+
end
|
37
|
+
|
38
|
+
it "responds with nil when it encounters a non-hash in the path" do
|
39
|
+
@result_data.get_leaf('three_level', 'level_two', 'level_one', 'level_zero').must_be_nil
|
40
|
+
end
|
41
|
+
|
42
|
+
it "evaluates a block instead of returning nil if a block is given" do
|
43
|
+
@result_data.get_leaf('nope') { 'potato' }.must_equal 'potato'
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: deep_tree
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Patrick Tulskie
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-12-03 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: minitest
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
description: DeepTree simplifies fetching deeply nested nodes in Ruby hashes.
|
28
|
+
email:
|
29
|
+
- patricktulskie@gmail.com
|
30
|
+
executables: []
|
31
|
+
extensions: []
|
32
|
+
extra_rdoc_files: []
|
33
|
+
files:
|
34
|
+
- .gitignore
|
35
|
+
- Gemfile
|
36
|
+
- LICENSE.txt
|
37
|
+
- README.md
|
38
|
+
- Rakefile
|
39
|
+
- benchmarks/bad_path.rb
|
40
|
+
- benchmarks/benchmark_helper.rb
|
41
|
+
- benchmarks/good_path.rb
|
42
|
+
- deep_tree.gemspec
|
43
|
+
- lib/deep_tree.rb
|
44
|
+
- lib/deep_tree/deep_tree.rb
|
45
|
+
- lib/deep_tree/hash.rb
|
46
|
+
- lib/deep_tree/version.rb
|
47
|
+
- spec/deep_tree_spec.rb
|
48
|
+
- spec/hash_spec.rb
|
49
|
+
- spec/spec_helper.rb
|
50
|
+
homepage: https://github.com/patricktulskie/deep_tree
|
51
|
+
licenses:
|
52
|
+
- MIT
|
53
|
+
metadata: {}
|
54
|
+
post_install_message:
|
55
|
+
rdoc_options: []
|
56
|
+
require_paths:
|
57
|
+
- lib
|
58
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - '>='
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '0'
|
63
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - '>='
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '0'
|
68
|
+
requirements: []
|
69
|
+
rubyforge_project:
|
70
|
+
rubygems_version: 2.0.3
|
71
|
+
signing_key:
|
72
|
+
specification_version: 4
|
73
|
+
summary: Aids with finding deeply nested nodes in Ruby hashes.
|
74
|
+
test_files:
|
75
|
+
- spec/deep_tree_spec.rb
|
76
|
+
- spec/hash_spec.rb
|
77
|
+
- spec/spec_helper.rb
|