rambling-trie 0.4.2 → 0.5.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 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