rambling-trie 0.5.0 → 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.markdown CHANGED
@@ -28,33 +28,55 @@ gem 'rambling-trie'
28
28
 
29
29
  ## How to use the Rambling Trie
30
30
 
31
+ To create the trie, initialize it like this:
32
+
33
+ ``` ruby
34
+ trie = Rambling::Trie.create
35
+ ```
36
+
31
37
  - - -
32
- ### Deprecation warnings
38
+
39
+ #### Deprecation warnings
33
40
 
34
41
  * 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.
42
+
36
43
  - - -
37
44
 
38
- To create the trie, initialize it like this:
45
+ You can also provide a block and the created instance will be yielded for you to perform any operation on it:
39
46
 
40
47
  ``` ruby
41
- trie = Rambling::Trie.create
48
+ Rambling::Trie.create do |trie|
49
+ trie << 'word'
50
+ end
42
51
  ```
43
52
 
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:
53
+ Additionally, you can 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:
45
54
 
46
55
  ``` ruby
47
56
  trie = Rambling::Trie.create '/path/to/file'
48
57
  ```
49
58
 
50
- You can also provide a block and the created instance will be yielded for you to perform any operation on it:
59
+ By default, a plain text file with the following format will be expected:
51
60
 
52
- ``` ruby
53
- Rambling::Trie.create do |trie|
54
- trie << 'word'
55
- end
61
+ ``` text
62
+ some
63
+ words
64
+ to
65
+ populate
66
+ the
67
+ trie
56
68
  ```
57
69
 
70
+ If you want to use a custom file format, you will need to provide a custom file reader that defines the `each_word` method that yields each word contained in the file. Look at the `Rambling::Trie::PlainTextReader` class for an example.
71
+
72
+ - - -
73
+
74
+ #### Deprecation warnings
75
+
76
+ * 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.
77
+
78
+ - - -
79
+
58
80
  To add new words to the trie, use `add` or `<<`:
59
81
 
60
82
  ``` ruby
data/lib/rambling/trie.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  %w{
2
- invalid_operation children_hash_deferer compressor
3
- branches enumerable inspector node root version
2
+ branches children_hash_deferer compressor enumerable
3
+ plain_text_reader inspector invalid_operation node
4
+ root version
4
5
  }.map { |file| File.join 'rambling', 'trie', file }.each &method(:require)
5
6
 
6
7
  # General namespace for all Rambling gems.
@@ -9,20 +10,15 @@ module Rambling
9
10
  module Trie
10
11
  class << self
11
12
  # Creates a new Trie. Entry point for the Rambling::Trie API.
12
- # @param [String, nil] filename the file to load the words from.
13
+ # @param [String, nil] filepath the file to load the words from.
13
14
  # @return [Root] the trie just created.
14
15
  # @yield [Root] the trie just created.
15
- def create(filename = nil)
16
+ def create(filepath = nil, reader = PlainTextReader.new)
16
17
  Root.new do |root|
17
- words_from(filename) { |word| root << word } if filename
18
+ reader.each_word(filepath) { |word| root << word } if filepath
18
19
  yield root if block_given?
19
20
  end
20
21
  end
21
-
22
- private
23
- def words_from(filename)
24
- File.open(filename) { |file| file.each_line { |line| yield line.chomp } }
25
- end
26
22
  end
27
23
  end
28
24
  end
@@ -10,19 +10,19 @@ module Rambling
10
10
  def add(word)
11
11
  raise InvalidOperation, 'Cannot add branch to compressed trie' if compressed?
12
12
  if word.empty?
13
- @terminal = true
13
+ self.terminal = true
14
14
  return
15
15
  end
16
16
 
17
17
  first_letter = word.slice(0).to_sym
18
18
 
19
- if @children.has_key? first_letter
19
+ if children.has_key? first_letter
20
20
  word.slice! 0
21
- child = @children[first_letter]
21
+ child = children[first_letter]
22
22
  child << word
23
23
  child
24
24
  else
25
- @children[first_letter] = Node.new word, self
25
+ children[first_letter] = Node.new word, self
26
26
  end
27
27
  end
28
28
 
@@ -41,7 +41,7 @@ module Rambling
41
41
  current_key, current_key_string = current_key first_letter
42
42
 
43
43
  unless current_key.nil?
44
- return @children[current_key].branch_when_compressed?(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
45
45
 
46
46
  while not chars.empty?
47
47
  char = chars.slice! 0
@@ -50,7 +50,7 @@ module Rambling
50
50
 
51
51
  return true if chars.empty?
52
52
  first_letter << char
53
- return @children[current_key].branch_when_compressed?(chars) if current_key_string.length == first_letter.length
53
+ return children[current_key].branch_when_compressed?(chars) if current_key_string.length == first_letter.length
54
54
  end
55
55
  end
56
56
 
@@ -68,7 +68,7 @@ module Rambling
68
68
  while not chars.empty?
69
69
  first_letter << chars.slice!(0)
70
70
  key = first_letter.to_sym
71
- return @children[key].word_when_compressed?(chars) if @children.has_key? key
71
+ return children[key].word_when_compressed?(chars) if children.has_key? key
72
72
  end
73
73
 
74
74
  false
@@ -79,7 +79,7 @@ module Rambling
79
79
  def current_key(letter)
80
80
  current_key_string = current_key = nil
81
81
 
82
- @children.keys.each do |key|
82
+ children.keys.each do |key|
83
83
  key_string = key.to_s
84
84
  if key_string.start_with? letter
85
85
  current_key = key
@@ -95,7 +95,7 @@ module Rambling
95
95
  first_letter = chars.slice! 0
96
96
  unless first_letter.nil?
97
97
  first_letter_sym = first_letter.to_sym
98
- return @children[first_letter_sym].send(method, chars) if @children.has_key? first_letter_sym
98
+ return children[first_letter_sym].send(method, chars) if children.has_key? first_letter_sym
99
99
  end
100
100
 
101
101
  false
@@ -6,7 +6,7 @@ module Rambling
6
6
  # @param [Symbol] key the key to look for in the children's hash.
7
7
  # @return [Node, nil] the child node with that key or nil.
8
8
  def [](key)
9
- @children[key]
9
+ children[key]
10
10
  end
11
11
 
12
12
  # Proxies to @children[key] = value.
@@ -14,21 +14,21 @@ module Rambling
14
14
  # @param [Node] value the node to add to the children's hash.
15
15
  # @return [Node, nil] the child node with that key or nil.
16
16
  def []=(key, value)
17
- @children[key] = value
17
+ children[key] = value
18
18
  end
19
19
 
20
20
  # Proxies to @children.delete(key)
21
21
  # @param [Symbol] key the key to delete in the children's hash.
22
22
  # @return [Node, nil] the child node corresponding to the key just deleted or nil.
23
23
  def delete(key)
24
- @children.delete(key)
24
+ children.delete(key)
25
25
  end
26
26
 
27
27
  # Proxies to @children.has_key?(key)
28
28
  # @param [Symbol] key the key to look for in the children's hash.
29
29
  # @return [Boolean] `true` for the keys that exist in the children's hash, false otherwise.
30
30
  def has_key?(key)
31
- @children.has_key?(key)
31
+ children.has_key?(key)
32
32
  end
33
33
  end
34
34
  end
@@ -5,18 +5,18 @@ module Rambling
5
5
  # Flag for compressed tries.
6
6
  # @return [Boolean] `true` for compressed tries, `false` otherwise.
7
7
  def compressed?
8
- @parent.nil? ? false : @parent.compressed?
8
+ parent and parent.compressed?
9
9
  end
10
10
 
11
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 @letter
15
- merge_with! @children.values.first
14
+ if children.size == 1 and not terminal? and letter
15
+ merge_with! children.values.first
16
16
  compress_tree!
17
17
  end
18
18
 
19
- @children.values.each &:compress_tree!
19
+ children.values.each &:compress_tree!
20
20
 
21
21
  self
22
22
  end
@@ -24,25 +24,25 @@ 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
- rehash_on_parent! @letter, new_letter
29
+ rehash_on_parent! letter, new_letter
30
30
  redefine_self! new_letter, child
31
31
 
32
- @children.values.each { |node| node.parent = self }
32
+ children.values.each { |node| node.parent = self }
33
33
  end
34
34
 
35
35
  def rehash_on_parent!(old_letter, new_letter)
36
- return if @parent.nil?
36
+ return if parent.nil?
37
37
 
38
- @parent.delete old_letter
39
- @parent[new_letter] = self
38
+ parent.delete old_letter
39
+ parent[new_letter] = self
40
40
  end
41
41
 
42
42
  def redefine_self!(new_letter, merged_node)
43
- @letter = new_letter
44
- @children = merged_node.children
45
- @terminal = merged_node.terminal?
43
+ self.letter = new_letter
44
+ self.children = merged_node.children
45
+ self.terminal = merged_node.terminal?
46
46
  end
47
47
  end
48
48
  end
@@ -2,6 +2,7 @@ module Rambling
2
2
  module Trie
3
3
  # Provides pretty printing behavior for the Trie data structure.
4
4
  module Inspector
5
+ # @return [String] a string representation of the current node.
5
6
  def inspect
6
7
  "#<#{self.class.name} letter: #{letter.inspect or 'nil'}, children: #{children.keys}>"
7
8
  end
@@ -1,6 +1,6 @@
1
1
  module Rambling
2
2
  module Trie
3
- # Raised when trying to execute an invalid operation for this Trie data structure.
3
+ # Raised when trying to execute an invalid operation on a Trie data structure.
4
4
  class InvalidOperation < Exception
5
5
  def initialize(message = nil)
6
6
  super
@@ -24,12 +24,13 @@ module Rambling
24
24
  # @param [String, nil] word the word from which to create this Node and his branch.
25
25
  # @param [Node, nil] parent the parent of this node.
26
26
  def initialize(word = nil, parent = nil)
27
- @letter, @parent, @terminal, @children = [nil, parent, false, {}]
27
+ self.parent = parent
28
+ self.children = {}
28
29
 
29
30
  unless word.nil? or word.empty?
30
31
  letter = word.slice! 0
31
- @letter = letter.to_sym unless letter.nil?
32
- @terminal = word.empty?
32
+ self.letter = letter.to_sym if letter
33
+ self.terminal = word.empty?
33
34
  self << word
34
35
  end
35
36
  end
@@ -37,20 +38,24 @@ module Rambling
37
38
  # Flag for terminal nodes.
38
39
  # @return [Boolean] `true` for terminal nodes, `false` otherwise.
39
40
  def terminal?
40
- @terminal
41
+ !!terminal
41
42
  end
42
43
 
43
44
  # String representation of the current node, if it is a terminal node.
44
45
  # @return [String] the string representation of the current node.
45
46
  # @raise [InvalidOperation] if node is not terminal or is root.
46
47
  def as_word
47
- raise InvalidOperation, 'Cannot represent branch as a word' unless @letter.nil? or terminal?
48
- get_letter_string
48
+ raise InvalidOperation, 'Cannot represent branch as a word' unless letter.nil? or terminal?
49
+ letter_string
49
50
  end
50
51
 
51
52
  protected
52
- def get_letter_string
53
- (@parent.nil? ? '' : @parent.get_letter_string) << @letter.to_s
53
+
54
+ attr_writer :letter, :children
55
+ attr_accessor :terminal
56
+
57
+ def letter_string
58
+ (parent ? parent.letter_string : '') << letter.to_s
54
59
  end
55
60
  end
56
61
  end
@@ -0,0 +1,23 @@
1
+ module Rambling
2
+ module Trie
3
+ # File reader for .txt files
4
+ class PlainTextReader
5
+ # Yields each word read from a .txt file
6
+ # @param [String] filepath the full path of the file to load the words from.
7
+ # @yield [String] Each line read from the file.
8
+ def each_word(filepath)
9
+ each_line(filepath) { |line| yield line.chomp }
10
+ end
11
+
12
+ private
13
+
14
+ def each_line(filepath)
15
+ open(filepath) { |file| file.each_line { |line| yield line } }
16
+ end
17
+
18
+ def open(filepath)
19
+ File.open(filepath) { |file| yield file }
20
+ end
21
+ end
22
+ end
23
+ end
@@ -6,35 +6,35 @@ module Rambling
6
6
  # @yield [Root] the trie just created.
7
7
  def initialize
8
8
  super
9
- @compressed = false
9
+ self.compressed = false
10
10
  yield self if block_given?
11
11
  end
12
12
 
13
13
  # Compresses the existing tree using redundant node elimination. Flags the trie as compressed.
14
14
  # @return [Root] self
15
15
  def compress!
16
- @compressed = (compressed? or not compress_tree!.nil?)
16
+ self.compressed = (compressed? or !!compress_tree!)
17
17
  self
18
18
  end
19
19
 
20
20
  # Flag for compressed tries. Overrides {Compressor#compressed?}.
21
21
  # @return [Boolean] `true` for compressed tries, `false` otherwise.
22
22
  def compressed?
23
- @compressed
23
+ !!compressed
24
24
  end
25
25
 
26
26
  # Checks if a path for a word or partial word exists in the trie.
27
27
  # @param [String] word the word or partial word to look for in the trie.
28
28
  # @return [Boolean] `true` if the word or partial word is found, `false` otherwise.
29
29
  def branch?(word = '')
30
- fulfills_condition? word, :branch?
30
+ is? :branch, word
31
31
  end
32
32
 
33
33
  # Checks if a whole word exists in the trie.
34
34
  # @param [String] word the word to look for in the trie.
35
35
  # @return [Boolean] `true` only if the word is found and the last character corresponds to a terminal node.
36
36
  def word?(word = '')
37
- fulfills_condition? word, :word?
37
+ is? :word, word
38
38
  end
39
39
 
40
40
  alias_method :include?, :word?
@@ -53,8 +53,9 @@ module Rambling
53
53
 
54
54
  private
55
55
 
56
- def fulfills_condition?(word, method)
57
- method = method.to_s.slice 0...(method.length - 1)
56
+ attr_accessor :compressed
57
+
58
+ def is?(method, word)
58
59
  method = compressed? ? "#{method}_when_compressed?" : "#{method}_when_uncompressed?"
59
60
  send method, word.chars.to_a
60
61
  end
@@ -1,6 +1,6 @@
1
1
  module Rambling
2
2
  module Trie
3
3
  # Current version of the rambling-trie.
4
- VERSION = '0.5.0'
4
+ VERSION = '0.5.1'
5
5
  end
6
6
  end
@@ -0,0 +1,20 @@
1
+ require 'spec_helper'
2
+
3
+ describe Rambling::Trie do
4
+ describe 'when a filepath is provided' do
5
+ let(:filepath) { File.join(::SPEC_ROOT, 'assets', 'test_words.txt') }
6
+ let(:words) { File.readlines(filepath).map &:chomp }
7
+ subject { Rambling::Trie.create filepath }
8
+
9
+ it 'contains all the words from the file' do
10
+ words.each { |word| expect(subject).to include(word) }
11
+ end
12
+
13
+ describe 'and the trie is compressed' do
14
+ it 'still contains all the words from the file' do
15
+ subject.compress!
16
+ words.each { |word| expect(subject).to include(word) }
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,18 @@
1
+ require 'spec_helper'
2
+
3
+ module Rambling
4
+ module Trie
5
+ describe PlainTextReader do
6
+ describe '#each_word' do
7
+ let(:filepath) { File.join(::SPEC_ROOT, 'assets', 'test_words.txt') }
8
+ let(:words) { File.readlines(filepath).map &:chomp }
9
+
10
+ it 'yields every word yielded by the file' do
11
+ yielded_words = []
12
+ subject.each_word(filepath) { |word| yielded_words << word }
13
+ expect(yielded_words).to eq(words)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -19,14 +19,20 @@ module Rambling
19
19
  end
20
20
  end
21
21
 
22
- context 'with a filename' do
23
- let(:filename) { File.join(::SPEC_ROOT, 'assets', 'test_words.txt') }
24
- let(:words) { File.readlines(filename).map &:chomp }
22
+ context 'with a filepath' do
23
+ let(:filepath) { 'test_words.txt' }
24
+ let(:reader) { double(Trie::PlainTextReader) }
25
+ let(:words) { %w(a couple of test words over here) }
26
+
27
+ before do
28
+ yielder = reader.stub(:each_word)
29
+ words.each { |word| yielder = yielder.and_yield(word) }
30
+ end
25
31
 
26
32
  it 'loads every word' do
27
33
  words.each { |word| root.should_receive(:<<).with(word) }
28
34
 
29
- Trie.create filename
35
+ Trie.create filepath, reader
30
36
  end
31
37
  end
32
38
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rambling-trie
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.5.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-12-18 00:00:00.000000000 Z
12
+ date: 2012-12-25 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rspec
@@ -118,6 +118,7 @@ files:
118
118
  - lib/rambling/trie/inspector.rb
119
119
  - lib/rambling/trie/invalid_operation.rb
120
120
  - lib/rambling/trie/node.rb
121
+ - lib/rambling/trie/plain_text_reader.rb
121
122
  - lib/rambling/trie/root.rb
122
123
  - lib/rambling/trie/tasks/gem.rb
123
124
  - lib/rambling/trie/tasks/performance.rb
@@ -125,11 +126,13 @@ files:
125
126
  - rambling-trie.gemspec
126
127
  - reports/performance
127
128
  - spec/assets/test_words.txt
129
+ - spec/integration/rambling/trie_spec.rb
128
130
  - spec/lib/rambling/trie/branches_spec.rb
129
131
  - spec/lib/rambling/trie/children_hash_deferer_spec.rb
130
132
  - spec/lib/rambling/trie/enumerable_spec.rb
131
133
  - spec/lib/rambling/trie/inspector_spec.rb
132
134
  - spec/lib/rambling/trie/node_spec.rb
135
+ - spec/lib/rambling/trie/plain_text_reader_spec.rb
133
136
  - spec/lib/rambling/trie/root_spec.rb
134
137
  - spec/lib/rambling/trie_spec.rb
135
138
  - spec/spec_helper.rb
@@ -147,7 +150,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
147
150
  version: '0'
148
151
  segments:
149
152
  - 0
150
- hash: 2034163289446234430
153
+ hash: -2285345975138821646
151
154
  required_rubygems_version: !ruby/object:Gem::Requirement
152
155
  none: false
153
156
  requirements:
@@ -156,7 +159,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
156
159
  version: '0'
157
160
  segments:
158
161
  - 0
159
- hash: 2034163289446234430
162
+ hash: -2285345975138821646
160
163
  requirements: []
161
164
  rubyforge_project:
162
165
  rubygems_version: 1.8.24
@@ -165,11 +168,13 @@ specification_version: 3
165
168
  summary: A custom implementation of the trie data structure.
166
169
  test_files:
167
170
  - spec/assets/test_words.txt
171
+ - spec/integration/rambling/trie_spec.rb
168
172
  - spec/lib/rambling/trie/branches_spec.rb
169
173
  - spec/lib/rambling/trie/children_hash_deferer_spec.rb
170
174
  - spec/lib/rambling/trie/enumerable_spec.rb
171
175
  - spec/lib/rambling/trie/inspector_spec.rb
172
176
  - spec/lib/rambling/trie/node_spec.rb
177
+ - spec/lib/rambling/trie/plain_text_reader_spec.rb
173
178
  - spec/lib/rambling/trie/root_spec.rb
174
179
  - spec/lib/rambling/trie_spec.rb
175
180
  - spec/spec_helper.rb