rambling-trie 0.4.2 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -21,3 +21,9 @@ pkg/
21
21
  # Yard
22
22
  doc/
23
23
  .yardoc/
24
+
25
+ # CTags
26
+ tags
27
+
28
+ # General
29
+ tmp/
data/.rspec CHANGED
File without changes
data/.travis.yml CHANGED
File without changes
data/Gemfile CHANGED
@@ -9,5 +9,7 @@ end
9
9
  group :test do
10
10
  gem 'rake'
11
11
  gem 'guard-rspec'
12
+ gem 'rb-fsevent'
13
+ gem 'rb-inotify'
12
14
  gem 'simplecov', require: false
13
15
  end
data/Guardfile CHANGED
@@ -1,7 +1,7 @@
1
1
  # A sample Guardfile
2
2
  # More info at https://github.com/guard/guard#readme
3
3
 
4
- guard 'rspec', version: 2, all_on_start: true, all_after_pass: false do
4
+ guard 'rspec', all_on_start: true, all_after_pass: false do
5
5
  watch(%r{^spec/.+_spec\.rb$})
6
6
  watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
7
7
  watch('spec/spec_helper.rb') { "spec" }
data/LICENSE CHANGED
File without changes
data/README.markdown CHANGED
@@ -1,4 +1,4 @@
1
- # Rambling Trie [![Build Status](https://secure.travis-ci.org/egonzalez0787/rambling-trie.png)](http://travis-ci.org/egonzalez0787/rambling-trie) [![Dependency Status](https://gemnasium.com/egonzalez0787/rambling-trie.png)](https://gemnasium.com/egonzalez0787/rambling-trie) [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/egonzalez0787/rambling-trie)
1
+ # Rambling Trie [![Build Status](https://secure.travis-ci.org/gonzedge/rambling-trie.png)](http://travis-ci.org/gonzedge/rambling-trie) [![Dependency Status](https://gemnasium.com/gonzedge/rambling-trie.png)](https://gemnasium.com/gonzedge/rambling-trie) [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/gonzedge/rambling-trie)
2
2
 
3
3
  The Rambling Trie is a custom implementation of the Trie data structure with Ruby, which includes compression abilities and is designed to be very fast to traverse.
4
4
 
@@ -29,9 +29,10 @@ gem 'rambling-trie'
29
29
  ## How to use the Rambling Trie
30
30
 
31
31
  - - -
32
- ### Deprecation warning
32
+ ### Deprecation warnings
33
33
 
34
- Starting from version 0.4.0, `Rambling::Trie.new` is deprecated. Please use `Rambling::Trie.create` instead.
34
+ * Starting from version 0.4.0, `Rambling::Trie.new` is deprecated. Please use `Rambling::Trie.create` instead.
35
+ * Starting from version 0.5.0, the `has_branch_for?`, `is_word?` and `add_branch_from` methods are deprecated. The methods `branch?`, `word?` and `add` should be used respectively.
35
36
  - - -
36
37
 
37
38
  To create the trie, initialize it like this:
@@ -40,30 +41,38 @@ To create the trie, initialize it like this:
40
41
  trie = Rambling::Trie.create
41
42
  ```
42
43
 
43
- You can also provide a file which contains all the words to be added to the trie, and it will read the file and create the structure for you, like this:
44
+ You can also provide the path to a file that contains all the words to be added to the trie, and it will read the file and create the complete structure for you, like this:
44
45
 
45
46
  ``` ruby
46
47
  trie = Rambling::Trie.create '/path/to/file'
47
48
  ```
48
49
 
49
- To add new words to the trie, use `add_branch_from` or `<<`:
50
+ You can also provide a block and the created instance will be yielded for you to perform any operation on it:
50
51
 
51
52
  ``` ruby
52
- trie.add_branch_from 'word'
53
+ Rambling::Trie.create do |trie|
54
+ trie << 'word'
55
+ end
56
+ ```
57
+
58
+ To add new words to the trie, use `add` or `<<`:
59
+
60
+ ``` ruby
61
+ trie.add 'word'
53
62
  trie << 'word'
54
63
  ```
55
64
 
56
- And to find out if a word already exists in the trie, use `is_word?` or `include?`:
65
+ And to find out if a word already exists in the trie, use `word?` or `include?`:
57
66
 
58
67
  ``` ruby
59
- trie.is_word? 'word'
68
+ trie.word? 'word'
60
69
  trie.include? 'word'
61
70
  ```
62
71
 
63
- If you wish to find if part of a word exists in the trie instance, you should call `has_branch_for?`:
72
+ If you wish to find if part of a word exists in the trie instance, you should call `branch?`:
64
73
 
65
74
  ``` ruby
66
- trie.has_branch_for? 'partial_word'
75
+ trie.branch? 'partial_word'
67
76
  ```
68
77
 
69
78
  ### Compression
@@ -78,7 +87,7 @@ trie.compress!
78
87
 
79
88
  This will reduce the amount of Trie nodes by eliminating the redundant ones, which are the only-child non-terminal nodes.
80
89
 
81
- Starting from version 0.3.2, the `has_branch_for?` and `is_word?` methods work as expected on a compressed trie.
90
+ Starting from version 0.3.2, the `has_branch_for?` (now `has_branch?`) and `is_word?` (now `word?`) methods work as expected on a compressed trie.
82
91
 
83
92
  __Note that the `compress!` method acts over the trie instance it belongs to.__
84
93
  __Also, adding words after compression is not supported.__
@@ -89,9 +98,22 @@ You can find out if a trie instance is compressed by calling the `compressed?` m
89
98
  trie.compressed?
90
99
  ```
91
100
 
101
+ ### Enumeration
102
+
103
+ Starting from version 0.4.2, you can use any `Enumerable` method over a trie instance, and it will iterate over each word contained in the trie. You can now do things like:
104
+
105
+ ``` ruby
106
+ trie.each do |word|
107
+ puts word
108
+ end
109
+
110
+ trie.any? { |word| word.include? 'x' }
111
+ # etc.
112
+ ```
113
+
92
114
  ## Further Documentation
93
115
 
94
- You can find further API documentation on the autogenerated [rambling-trie gem RubyDoc.info page](http://rubydoc.info/gems/rambling-trie) or if you want edge documentation, you can go the [GitHub project RubyDoc.info page](http://rubydoc.info/github/egonzalez0787/rambling-trie).
116
+ You can find further API documentation on the autogenerated [rambling-trie gem RubyDoc.info page](http://rubydoc.info/gems/rambling-trie) or if you want edge documentation, you can go the [GitHub project RubyDoc.info page](http://rubydoc.info/github/gonzedge/rambling-trie).
95
117
 
96
118
  ## Compatible Ruby and Rails versions
97
119
 
@@ -105,7 +127,8 @@ And the following Rails versions:
105
127
  * 3.1.x
106
128
  * 3.2.x
107
129
 
108
- It's possible that Ruby 1.8.7 and Rails 3.0.x are supported, but there is no guarantee.
130
+ It's possible that Rails 3.0.x is supported, but there is no guarantee.
131
+ Ruby 1.8.7 is not supported.
109
132
 
110
133
  ## Contributing to Rambling Trie
111
134
 
data/Rakefile CHANGED
File without changes
File without changes
data/lib/rambling-trie.rb CHANGED
File without changes
data/lib/rambling/trie.rb CHANGED
@@ -1,13 +1,7 @@
1
- [
2
- 'invalid_operation',
3
- 'children_hash_deferer',
4
- 'compressor',
5
- 'branches',
6
- 'enumerable',
7
- 'node',
8
- 'root',
9
- 'version'
10
- ].map { |file| File.join 'rambling', 'trie', file }.each &method(:require)
1
+ %w{
2
+ invalid_operation children_hash_deferer compressor
3
+ branches enumerable inspector node root version
4
+ }.map { |file| File.join 'rambling', 'trie', file }.each &method(:require)
11
5
 
12
6
  # General namespace for all Rambling gems.
13
7
  module Rambling
@@ -15,18 +9,19 @@ module Rambling
15
9
  module Trie
16
10
  class << self
17
11
  # Creates a new Trie. Entry point for the Rambling::Trie API.
18
- # @param [String, nil] filename the file to load the words from (defaults to nil).
12
+ # @param [String, nil] filename the file to load the words from.
13
+ # @return [Root] the trie just created.
14
+ # @yield [Root] the trie just created.
19
15
  def create(filename = nil)
20
- Root.new filename
16
+ Root.new do |root|
17
+ words_from(filename) { |word| root << word } if filename
18
+ yield root if block_given?
19
+ end
21
20
  end
22
21
 
23
- # Creates a new Trie. Entry point for the Rambling::Trie API.
24
- # @param [String, nil] filename the file to load the words from (defaults to nil).
25
- # @deprecated Please use {.create} instead.
26
- # @see .create
27
- def new(filename = nil)
28
- warn '[DEPRECATION] `new` is deprecated. Please use `create` instead.'
29
- create filename
22
+ private
23
+ def words_from(filename)
24
+ File.open(filename) { |file| file.each_line { |line| yield line.chomp } }
30
25
  end
31
26
  end
32
27
  end
@@ -7,8 +7,8 @@ module Rambling
7
7
  # @return [Node] the just added branch's root node.
8
8
  # @raise [InvalidOperation] if the trie is already compressed.
9
9
  # @note This method clears the contents of the word variable.
10
- def add_branch_from(word)
11
- raise InvalidOperation.new('Cannot add branch to compressed trie') if compressed?
10
+ def add(word)
11
+ raise InvalidOperation, 'Cannot add branch to compressed trie' if compressed?
12
12
  if word.empty?
13
13
  @terminal = true
14
14
  return
@@ -26,56 +26,49 @@ module Rambling
26
26
  end
27
27
  end
28
28
 
29
- # Alias for {#add_branch_from}. Defined instead of simple `alias_method` for overriding purposes.
30
- # @param [String] word the word to add the branch from.
31
- # @return [Node] the just added branch's root node.
32
- # @raise [InvalidOperation] if the trie is already compressed.
33
- # @see #add_branch_from
34
- def <<(word)
35
- add_branch_from word
36
- end
29
+ alias_method :<<, :add
37
30
 
38
31
  protected
39
32
 
40
- def uncompressed_has_branch_for?(chars)
41
- chars.empty? or fulfills_uncompressed_condition?(:uncompressed_has_branch_for?, chars)
33
+ def branch_when_uncompressed?(chars)
34
+ chars.empty? or fulfills_uncompressed_condition?(:branch_when_uncompressed?, chars)
42
35
  end
43
36
 
44
- def compressed_has_branch_for?(chars)
37
+ def branch_when_compressed?(chars)
45
38
  return true if chars.empty?
46
39
 
47
40
  first_letter = chars.slice! 0
48
41
  current_key, current_key_string = current_key first_letter
49
42
 
50
43
  unless current_key.nil?
51
- return @children[current_key].compressed_has_branch_for?(chars) if current_key_string.length == first_letter.length
44
+ return @children[current_key].branch_when_compressed?(chars) if current_key_string.length == first_letter.length
52
45
 
53
46
  while not chars.empty?
54
47
  char = chars.slice! 0
55
48
 
56
49
  break unless current_key_string[first_letter.length] == char
57
50
 
58
- first_letter += char
59
51
  return true if chars.empty?
60
- return @children[current_key].compressed_has_branch_for?(chars) if current_key_string.length == first_letter.length
52
+ first_letter << char
53
+ return @children[current_key].branch_when_compressed?(chars) if current_key_string.length == first_letter.length
61
54
  end
62
55
  end
63
56
 
64
57
  false
65
58
  end
66
59
 
67
- def uncompressed_is_word?(chars)
68
- (chars.empty? and terminal?) or fulfills_uncompressed_condition?(:uncompressed_is_word?, chars)
60
+ def word_when_uncompressed?(chars)
61
+ (chars.empty? and terminal?) or fulfills_uncompressed_condition?(:word_when_uncompressed?, chars)
69
62
  end
70
63
 
71
- def compressed_is_word?(chars)
64
+ def word_when_compressed?(chars)
72
65
  return true if chars.empty? and terminal?
73
66
 
74
67
  first_letter = ''
75
68
  while not chars.empty?
76
- first_letter += chars.slice! 0
69
+ first_letter << chars.slice!(0)
77
70
  key = first_letter.to_sym
78
- return @children[key].compressed_is_word?(chars) if @children.has_key? key
71
+ return @children[key].word_when_compressed?(chars) if @children.has_key? key
79
72
  end
80
73
 
81
74
  false
File without changes
@@ -8,10 +8,10 @@ module Rambling
8
8
  @parent.nil? ? false : @parent.compressed?
9
9
  end
10
10
 
11
- # Compressed the current node using redundant node elimination.
11
+ # Compress the current node using redundant node elimination.
12
12
  # @return [Root, Node] the compressed node.
13
13
  def compress_tree!
14
- if @children.size == 1 and not terminal? and not @letter.nil?
14
+ if @children.size == 1 and not terminal? and @letter
15
15
  merge_with! @children.values.first
16
16
  compress_tree!
17
17
  end
@@ -24,7 +24,7 @@ module Rambling
24
24
  private
25
25
 
26
26
  def merge_with!(child)
27
- new_letter = (@letter.to_s + child.letter.to_s).to_sym
27
+ new_letter = (@letter.to_s << child.letter.to_s).to_sym
28
28
 
29
29
  rehash_on_parent! @letter, new_letter
30
30
  redefine_self! new_letter, child
File without changes
@@ -0,0 +1,10 @@
1
+ module Rambling
2
+ module Trie
3
+ # Provides pretty printing behavior for the Trie data structure.
4
+ module Inspector
5
+ def inspect
6
+ "#<#{self.class.name} letter: #{letter.inspect or 'nil'}, children: #{children.keys}>"
7
+ end
8
+ end
9
+ end
10
+ end
File without changes
@@ -6,6 +6,7 @@ module Rambling
6
6
  include Compressor
7
7
  include Branches
8
8
  include Enumerable
9
+ include Inspector
9
10
 
10
11
  # Letter or letters corresponding to this node.
11
12
  # @return [Symbol, nil] the corresponding letter(s) or nil.
@@ -20,9 +21,9 @@ module Rambling
20
21
  attr_accessor :parent
21
22
 
22
23
  # Creates a new Node.
23
- # @param [String] word the word from which to create this Node and his branch.
24
- # @param [Node] parent the parent of this node.
25
- def initialize(word, parent = nil)
24
+ # @param [String, nil] word the word from which to create this Node and his branch.
25
+ # @param [Node, nil] parent the parent of this node.
26
+ def initialize(word = nil, parent = nil)
26
27
  @letter, @parent, @terminal, @children = [nil, parent, false, {}]
27
28
 
28
29
  unless word.nil? or word.empty?
@@ -43,13 +44,13 @@ module Rambling
43
44
  # @return [String] the string representation of the current node.
44
45
  # @raise [InvalidOperation] if node is not terminal or is root.
45
46
  def as_word
46
- raise InvalidOperation.new unless @letter.nil? or terminal?
47
+ raise InvalidOperation, 'Cannot represent branch as a word' unless @letter.nil? or terminal?
47
48
  get_letter_string
48
49
  end
49
50
 
50
51
  protected
51
52
  def get_letter_string
52
- (@parent.nil? ? '' : @parent.get_letter_string) + @letter.to_s
53
+ (@parent.nil? ? '' : @parent.get_letter_string) << @letter.to_s
53
54
  end
54
55
  end
55
56
  end
@@ -3,19 +3,17 @@ module Rambling
3
3
  # A representation of the root node in the Trie data structure.
4
4
  class Root < Node
5
5
  # Creates a new Trie.
6
- # @param [String, nil] filename the file to load the words from (defaults to nil).
7
- def initialize(filename = nil)
8
- super(nil)
9
-
10
- @filename = filename
6
+ # @yield [Root] the trie just created.
7
+ def initialize
8
+ super
11
9
  @compressed = false
12
- add_all_nodes if filename
10
+ yield self if block_given?
13
11
  end
14
12
 
15
13
  # Compresses the existing tree using redundant node elimination. Flags the trie as compressed.
16
- # @return [Root] same object
14
+ # @return [Root] self
17
15
  def compress!
18
- @compressed = (not compress_tree!.nil?) unless compressed?
16
+ @compressed = (compressed? or not compress_tree!.nil?)
19
17
  self
20
18
  end
21
19
 
@@ -28,37 +26,37 @@ module Rambling
28
26
  # Checks if a path for a word or partial word exists in the trie.
29
27
  # @param [String] word the word or partial word to look for in the trie.
30
28
  # @return [Boolean] `true` if the word or partial word is found, `false` otherwise.
31
- def has_branch_for?(word = '')
32
- fulfills_condition word, :has_branch_for?
29
+ def branch?(word = '')
30
+ fulfills_condition? word, :branch?
33
31
  end
34
32
 
35
33
  # Checks if a whole word exists in the trie.
36
34
  # @param [String] word the word to look for in the trie.
37
35
  # @return [Boolean] `true` only if the word is found and the last character corresponds to a terminal node.
38
- def is_word?(word = '')
39
- fulfills_condition word, :is_word?
36
+ def word?(word = '')
37
+ fulfills_condition? word, :word?
40
38
  end
41
39
 
42
- alias_method :include?, :is_word?
40
+ alias_method :include?, :word?
43
41
 
44
42
  # Adds a branch to the trie based on the word, without changing the passed word.
45
43
  # @param [String] word the word to add the branch from.
46
44
  # @return [Node] the just added branch's root node.
47
45
  # @raise [InvalidOperation] if the trie is already compressed.
48
- # @see Branches#add_branch_from
46
+ # @see Branches#add
49
47
  # @note Avoids clearing the contents of the word variable.
50
- def add_branch_from(word)
48
+ def add(word)
51
49
  super word.clone
52
50
  end
53
51
 
52
+ alias_method :<<, :add
53
+
54
54
  private
55
- def fulfills_condition(word, method)
56
- method = compressed? ? "compressed_#{method}" : "uncompressed_#{method}"
57
- send(method, word.chars.to_a)
58
- end
59
55
 
60
- def add_all_nodes
61
- File.open(@filename).each_line { |line| self << line.chomp }
56
+ def fulfills_condition?(word, method)
57
+ method = method.to_s.slice 0...(method.length - 1)
58
+ method = compressed? ? "#{method}_when_compressed?" : "#{method}_when_uncompressed?"
59
+ send method, word.chars.to_a
62
60
  end
63
61
  end
64
62
  end
File without changes
@@ -3,7 +3,7 @@ require 'benchmark'
3
3
  namespace :performance do
4
4
  def report(name, trie, output)
5
5
  words = ['hi', 'help', 'beautiful', 'impressionism', 'anthropological']
6
- methods = [:is_word?, :has_branch_for?]
6
+ methods = [:word?, :branch?]
7
7
 
8
8
  output.puts "==> #{name}"
9
9
  methods.each do |method|
@@ -21,7 +21,7 @@ namespace :performance do
21
21
  output.puts "\nReport for rambling-trie version #{Rambling::Trie::VERSION}"
22
22
 
23
23
  trie = nil
24
- measure = Benchmark.measure { trie = Rambling::Trie.create get_path('assets', 'dictionaries', 'words_with_friends.txt') }
24
+ measure = Benchmark.measure { trie = Rambling::Trie.create path('assets', 'dictionaries', 'words_with_friends.txt') }
25
25
 
26
26
  if ENV['profile_creation']
27
27
  output.puts '==> Creation'
@@ -39,8 +39,8 @@ namespace :performance do
39
39
  output.close
40
40
  end
41
41
 
42
- def get_path(*filename)
43
- File.join File.dirname(__FILE__), '..', '..', '..', *filename
42
+ def path(*filename)
43
+ File.join File.dirname(__FILE__), '..', '..', '..', '..', *filename
44
44
  end
45
45
 
46
46
  desc 'Generate performance report'
@@ -53,7 +53,7 @@ namespace :performance do
53
53
  desc 'Generate performance report and append result to reports/performance'
54
54
  task :save do
55
55
  puts 'Generating performance report...'
56
- generate_report get_path('reports', 'performance')
56
+ generate_report path('reports', 'performance')
57
57
  puts 'Report has been saved to reports/performance'
58
58
  end
59
59
  end
@@ -64,9 +64,9 @@ namespace :performance do
64
64
 
65
65
  puts 'Generating profiling reports...'
66
66
 
67
- rambling_trie = Rambling::Trie.create get_path('assets', 'dictionaries', 'words_with_friends.txt')
67
+ rambling_trie = Rambling::Trie.create path('assets', 'dictionaries', 'words_with_friends.txt')
68
68
  words = ['hi', 'help', 'beautiful', 'impressionism', 'anthropological']
69
- methods = [:has_branch_for?, :is_word?]
69
+ methods = [:branch?, :word?]
70
70
  tries = [lambda {rambling_trie.clone}, lambda {rambling_trie.clone.compress!}]
71
71
 
72
72
  methods.each do |method|
@@ -78,7 +78,7 @@ namespace :performance do
78
78
  end
79
79
  end
80
80
 
81
- File.open get_path('reports', "profile-#{trie.compressed? ? 'compressed' : 'uncompressed'}-#{method.to_s.sub(/\?/, '')}-#{Time.now.to_i}"), 'w' do |file|
81
+ File.open path('reports', "profile-#{trie.compressed? ? 'compressed' : 'uncompressed'}-#{method.to_s.sub(/\?/, '')}-#{Time.now.to_i}"), 'w' do |file|
82
82
  RubyProf::CallTreePrinter.new(result).print file
83
83
  end
84
84
  end
@@ -93,15 +93,15 @@ namespace :performance do
93
93
 
94
94
  puts 'Generating cpu profiling reports...'
95
95
 
96
- rambling_trie = Rambling::Trie.create get_path('assets', 'dictionaries', 'words_with_friends.txt')
96
+ rambling_trie = Rambling::Trie.create path('assets', 'dictionaries', 'words_with_friends.txt')
97
97
  words = ['hi', 'help', 'beautiful', 'impressionism', 'anthropological']
98
- methods = [:has_branch_for?, :is_word?]
98
+ methods = [:branch?, :word?]
99
99
  tries = [lambda {rambling_trie.clone}, lambda {rambling_trie.clone.compress!}]
100
100
 
101
101
  methods.each do |method|
102
102
  tries.each do |trie_generator|
103
103
  trie = trie_generator.call
104
- result = PerfTools::CpuProfiler.start get_path('reports', "cpu_profile-#{trie.compressed? ? 'compressed' : 'uncompressed'}-#{method.to_s.sub(/\?/, '')}-#{Time.now.to_i}") do
104
+ result = PerfTools::CpuProfiler.start path('reports', "cpu_profile-#{trie.compressed? ? 'compressed' : 'uncompressed'}-#{method.to_s.sub(/\?/, '')}-#{Time.now.to_i}") do
105
105
  words.each do |word|
106
106
  200_000.times { trie.send method, word }
107
107
  end