rambling-trie 0.4.0 → 0.4.1
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 +4 -0
- data/README.markdown +14 -8
- data/lib/rambling-trie.rb +16 -11
- data/lib/rambling-trie/branches.rb +38 -29
- data/lib/rambling-trie/children_hash_deferer.rb +28 -27
- data/lib/rambling-trie/compressor.rb +8 -10
- data/lib/rambling-trie/node.rb +12 -14
- data/lib/rambling-trie/root.rb +15 -18
- data/lib/rambling-trie/tasks/performance.rb +22 -14
- data/lib/rambling-trie/version.rb +1 -1
- data/rambling-trie.gemspec +1 -1
- data/spec/lib/rambling-trie/branches_spec.rb +52 -0
- data/spec/lib/rambling-trie/children_hash_deferer_spec.rb +59 -0
- data/spec/lib/rambling-trie/node_spec.rb +1 -34
- data/spec/lib/rambling-trie/root_spec.rb +30 -18
- data/spec/lib/rambling-trie_spec.rb +18 -1
- metadata +6 -5
- data/lib/rambling.rb +0 -3
data/.gitignore
CHANGED
data/README.markdown
CHANGED
@@ -28,31 +28,37 @@ gem 'rambling-trie'
|
|
28
28
|
|
29
29
|
## How to use the Rambling Trie
|
30
30
|
|
31
|
+
### Deprecation warning
|
32
|
+
|
33
|
+
Starting from version 0.4.0, `Rambling::Trie.new` is deprecated. Please use `Rambling::Trie.create` instead.
|
34
|
+
|
31
35
|
To create the trie, initialize it like this:
|
32
36
|
|
33
37
|
``` ruby
|
34
|
-
trie = Rambling::Trie.
|
38
|
+
trie = Rambling::Trie.create
|
35
39
|
```
|
36
40
|
|
37
41
|
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:
|
38
42
|
|
39
43
|
``` ruby
|
40
|
-
trie = Rambling::Trie.
|
44
|
+
trie = Rambling::Trie.create '/path/to/file'
|
41
45
|
```
|
42
46
|
|
43
|
-
To add new words to the trie, use `add_branch_from
|
47
|
+
To add new words to the trie, use `add_branch_from` or `<<`:
|
44
48
|
|
45
49
|
``` ruby
|
46
50
|
trie.add_branch_from 'word'
|
51
|
+
trie << 'word'
|
47
52
|
```
|
48
53
|
|
49
|
-
And to find out if a word already exists in the trie, use `is_word?`:
|
54
|
+
And to find out if a word already exists in the trie, use `is_word?` or `include?`:
|
50
55
|
|
51
56
|
``` ruby
|
52
57
|
trie.is_word? 'word'
|
58
|
+
trie.include? 'word'
|
53
59
|
```
|
54
60
|
|
55
|
-
If you wish to find if part of a word exists in the
|
61
|
+
If you wish to find if part of a word exists in the trie instance, you should call `has_branch_for?`:
|
56
62
|
|
57
63
|
``` ruby
|
58
64
|
trie.has_branch_for? 'partial_word'
|
@@ -62,7 +68,7 @@ trie.has_branch_for? 'partial_word'
|
|
62
68
|
|
63
69
|
By default, the Rambling Trie works as a Standard Trie.
|
64
70
|
Starting from version 0.1.0, you can obtain a Compressed Trie from the Standard one, by using the compression feature.
|
65
|
-
Just call the `compress!` method on the
|
71
|
+
Just call the `compress!` method on the trie instance:
|
66
72
|
|
67
73
|
``` ruby
|
68
74
|
trie.compress!
|
@@ -72,10 +78,10 @@ This will reduce the amount of Trie nodes by eliminating the redundant ones, whi
|
|
72
78
|
|
73
79
|
Starting from version 0.3.2, the `has_branch_for?` and `is_word?` methods work as expected on a compressed trie.
|
74
80
|
|
75
|
-
__Note that the `compress!` method acts over the
|
81
|
+
__Note that the `compress!` method acts over the trie instance it belongs to.__
|
76
82
|
__Also, adding words after compression is not supported.__
|
77
83
|
|
78
|
-
You can find out if a
|
84
|
+
You can find out if a trie instance is compressed by calling the `compressed?` method:
|
79
85
|
|
80
86
|
``` ruby
|
81
87
|
trie.compressed?
|
data/lib/rambling-trie.rb
CHANGED
@@ -1,22 +1,27 @@
|
|
1
1
|
[
|
2
|
-
'
|
3
|
-
'
|
4
|
-
'
|
5
|
-
'
|
6
|
-
'
|
7
|
-
'
|
8
|
-
'
|
9
|
-
|
10
|
-
].each do |file|
|
11
|
-
require File.join File.dirname(__FILE__), file
|
12
|
-
end
|
2
|
+
'invalid_operation',
|
3
|
+
'children_hash_deferer',
|
4
|
+
'compressor',
|
5
|
+
'branches',
|
6
|
+
'node',
|
7
|
+
'root',
|
8
|
+
'version'
|
9
|
+
].map { |file| File.join('rambling-trie', file) }.each &method(:require)
|
13
10
|
|
14
11
|
module Rambling
|
15
12
|
module Trie
|
16
13
|
class << self
|
14
|
+
# Creates a new Trie. Entry point for the Rambling::Trie API.
|
15
|
+
# @param [String, nil] filename the file to load the words from (defaults to nil).
|
17
16
|
def create(*params)
|
18
17
|
Root.new *params
|
19
18
|
end
|
19
|
+
|
20
|
+
# @deprecated Please use {#create} instead
|
21
|
+
def new(*params)
|
22
|
+
warn '[DEPRECATION] `new` is deprecated. Please use `create` instead.'
|
23
|
+
create *params
|
24
|
+
end
|
20
25
|
end
|
21
26
|
end
|
22
27
|
end
|
@@ -9,71 +9,65 @@ module Rambling
|
|
9
9
|
def add_branch_from(word)
|
10
10
|
raise InvalidOperation.new('Cannot add branch to compressed trie') if compressed?
|
11
11
|
if word.empty?
|
12
|
-
@
|
12
|
+
@terminal = true
|
13
13
|
return
|
14
14
|
end
|
15
15
|
|
16
16
|
first_letter = word.slice(0).to_sym
|
17
17
|
|
18
|
-
if @children.has_key?
|
19
|
-
word.slice!
|
18
|
+
if @children.has_key? first_letter
|
19
|
+
word.slice! 0
|
20
20
|
child = @children[first_letter]
|
21
|
-
child
|
21
|
+
child << word
|
22
22
|
child
|
23
23
|
else
|
24
24
|
@children[first_letter] = Node.new word, self
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
28
|
+
alias_method :<<, :add_branch_from
|
29
|
+
|
28
30
|
protected
|
29
31
|
|
30
|
-
def
|
31
|
-
chars.empty? or fulfills_uncompressed_condition?(:
|
32
|
+
def uncompressed_has_branch_for?(chars)
|
33
|
+
chars.empty? or fulfills_uncompressed_condition?(:uncompressed_has_branch_for?, chars)
|
32
34
|
end
|
33
35
|
|
34
|
-
def
|
36
|
+
def compressed_has_branch_for?(chars)
|
35
37
|
return true if chars.empty?
|
36
38
|
|
37
|
-
first_letter = chars.slice!
|
38
|
-
|
39
|
-
@children.keys.each do |x|
|
40
|
-
x = x.to_s
|
41
|
-
if x.start_with?(first_letter)
|
42
|
-
key = x
|
43
|
-
break
|
44
|
-
end
|
45
|
-
end
|
39
|
+
first_letter = chars.slice! 0
|
40
|
+
current_key, current_key_string = current_key first_letter
|
46
41
|
|
47
|
-
unless
|
48
|
-
|
49
|
-
return @children[sym_key].has_compressed_branch_for?(chars) if key.length == first_letter.length
|
42
|
+
unless current_key.nil?
|
43
|
+
return @children[current_key].compressed_has_branch_for?(chars) if current_key_string.length == first_letter.length
|
50
44
|
|
51
45
|
while not chars.empty?
|
52
|
-
char = chars.slice!
|
46
|
+
char = chars.slice! 0
|
53
47
|
|
54
|
-
break unless
|
48
|
+
break unless current_key_string[first_letter.length] == char
|
55
49
|
|
56
50
|
first_letter += char
|
57
51
|
return true if chars.empty?
|
58
|
-
return @children[
|
52
|
+
return @children[current_key].compressed_has_branch_for?(chars) if current_key_string.length == first_letter.length
|
59
53
|
end
|
60
54
|
end
|
61
55
|
|
62
56
|
false
|
63
57
|
end
|
64
58
|
|
65
|
-
def
|
66
|
-
(chars.empty? and terminal?) or fulfills_uncompressed_condition?(:
|
59
|
+
def uncompressed_is_word?(chars)
|
60
|
+
(chars.empty? and terminal?) or fulfills_uncompressed_condition?(:uncompressed_is_word?, chars)
|
67
61
|
end
|
68
62
|
|
69
|
-
def
|
63
|
+
def compressed_is_word?(chars)
|
70
64
|
return true if chars.empty? and terminal?
|
71
65
|
|
72
66
|
first_letter = ''
|
73
67
|
while not chars.empty?
|
74
|
-
first_letter += chars.slice!
|
68
|
+
first_letter += chars.slice! 0
|
75
69
|
key = first_letter.to_sym
|
76
|
-
return @children[key].
|
70
|
+
return @children[key].compressed_is_word?(chars) if @children.has_key? key
|
77
71
|
end
|
78
72
|
|
79
73
|
false
|
@@ -81,11 +75,26 @@ module Rambling
|
|
81
75
|
|
82
76
|
private
|
83
77
|
|
78
|
+
def current_key(letter)
|
79
|
+
current_key_string = current_key = nil
|
80
|
+
|
81
|
+
@children.keys.each do |key|
|
82
|
+
key_string = key.to_s
|
83
|
+
if key_string.start_with? letter
|
84
|
+
current_key = key
|
85
|
+
current_key_string = key_string
|
86
|
+
break
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
[current_key, current_key_string]
|
91
|
+
end
|
92
|
+
|
84
93
|
def fulfills_uncompressed_condition?(method, chars)
|
85
|
-
first_letter = chars.slice!
|
94
|
+
first_letter = chars.slice! 0
|
86
95
|
unless first_letter.nil?
|
87
96
|
first_letter_sym = first_letter.to_sym
|
88
|
-
return @children[first_letter_sym].send(method, chars) if @children.has_key?
|
97
|
+
return @children[first_letter_sym].send(method, chars) if @children.has_key? first_letter_sym
|
89
98
|
end
|
90
99
|
|
91
100
|
false
|
@@ -1,34 +1,35 @@
|
|
1
1
|
module Rambling
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
2
|
+
module Trie
|
3
|
+
# Provides some proxy methods to the children's hash for readability.
|
4
|
+
module ChildrenHashDeferer
|
5
|
+
# Proxies to @children[key]
|
6
|
+
# @param [Symbol] key the key to look for in the children's hash.
|
7
|
+
# @return [Node, nil] the child node with that key or nil.
|
8
|
+
def [](key)
|
9
|
+
@children[key]
|
10
|
+
end
|
10
11
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
12
|
+
# Proxies to @children[key] = value.
|
13
|
+
# @param [Symbol] key the to add or change the value for.
|
14
|
+
# @param [Node] value the node to add to the children's hash.
|
15
|
+
# @return [Node, nil] the child node with that key or nil.
|
16
|
+
def []=(key, value)
|
17
|
+
@children[key] = value
|
18
|
+
end
|
18
19
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
20
|
+
# Proxies to @children.delete(key)
|
21
|
+
# @param [Symbol] key the key to delete in the children's hash.
|
22
|
+
# @return [Node, nil] the child node corresponding to the key just deleted or nil.
|
23
|
+
def delete(key)
|
24
|
+
@children.delete(key)
|
25
|
+
end
|
25
26
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
27
|
+
# Proxies to @children.has_key?(key)
|
28
|
+
# @param [Symbol] key the key to look for in the children's hash.
|
29
|
+
# @return [Boolean] `true` for the keys that exist in the children's hash, false otherwise.
|
30
|
+
def has_key?(key)
|
31
|
+
@children.has_key?(key)
|
32
|
+
end
|
31
33
|
end
|
32
34
|
end
|
33
35
|
end
|
34
|
-
|
@@ -8,15 +8,13 @@ module Rambling
|
|
8
8
|
@parent.nil? ? false : @parent.compressed?
|
9
9
|
end
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
def compress_own_tree!
|
11
|
+
def compress_tree!
|
14
12
|
if @children.size == 1 and not terminal? and not @letter.nil?
|
15
|
-
merge_with!
|
16
|
-
|
13
|
+
merge_with! @children.values.first
|
14
|
+
compress_tree!
|
17
15
|
end
|
18
16
|
|
19
|
-
@children.values.each
|
17
|
+
@children.values.each &:compress_tree!
|
20
18
|
|
21
19
|
self
|
22
20
|
end
|
@@ -26,8 +24,8 @@ module Rambling
|
|
26
24
|
def merge_with!(child)
|
27
25
|
new_letter = (@letter.to_s + child.letter.to_s).to_sym
|
28
26
|
|
29
|
-
rehash_on_parent!
|
30
|
-
redefine_self!
|
27
|
+
rehash_on_parent! @letter, new_letter
|
28
|
+
redefine_self! new_letter, child
|
31
29
|
|
32
30
|
@children.values.each { |node| node.parent = self }
|
33
31
|
end
|
@@ -35,14 +33,14 @@ module Rambling
|
|
35
33
|
def rehash_on_parent!(old_letter, new_letter)
|
36
34
|
return if @parent.nil?
|
37
35
|
|
38
|
-
@parent.delete
|
36
|
+
@parent.delete old_letter
|
39
37
|
@parent[new_letter] = self
|
40
38
|
end
|
41
39
|
|
42
40
|
def redefine_self!(new_letter, merged_node)
|
43
41
|
@letter = new_letter
|
44
42
|
@children = merged_node.children
|
45
|
-
@
|
43
|
+
@terminal = merged_node.terminal?
|
46
44
|
end
|
47
45
|
end
|
48
46
|
end
|
data/lib/rambling-trie/node.rb
CHANGED
@@ -5,6 +5,7 @@ module Rambling
|
|
5
5
|
include ChildrenHashDeferer
|
6
6
|
include Compressor
|
7
7
|
include Branches
|
8
|
+
include Enumerable
|
8
9
|
|
9
10
|
# Letter or letters corresponding to this node.
|
10
11
|
# @return [Symbol, nil] the corresponding letter(s) or nil.
|
@@ -15,37 +16,34 @@ module Rambling
|
|
15
16
|
attr_reader :children
|
16
17
|
|
17
18
|
# Parent node.
|
18
|
-
# @return [
|
19
|
+
# @return [Node, nil] the parent node or nil for the root element.
|
19
20
|
attr_accessor :parent
|
20
21
|
|
21
|
-
# Creates a new
|
22
|
-
# @param [String] word the word from which to create this
|
23
|
-
# @param [
|
22
|
+
# 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.
|
24
25
|
def initialize(word, parent = nil)
|
25
|
-
@letter = nil
|
26
|
-
@parent = parent
|
27
|
-
@is_terminal = false
|
28
|
-
@children = {}
|
26
|
+
@letter, @parent, @terminal, @children = [nil, parent, false, {}]
|
29
27
|
|
30
28
|
unless word.nil? or word.empty?
|
31
|
-
letter = word.slice!
|
29
|
+
letter = word.slice! 0
|
32
30
|
@letter = letter.to_sym unless letter.nil?
|
33
|
-
@
|
34
|
-
|
31
|
+
@terminal = word.empty?
|
32
|
+
self << word
|
35
33
|
end
|
36
34
|
end
|
37
35
|
|
38
36
|
# Flag for terminal nodes.
|
39
37
|
# @return [Boolean] `true` for terminal nodes, `false` otherwise.
|
40
38
|
def terminal?
|
41
|
-
@
|
39
|
+
@terminal
|
42
40
|
end
|
43
41
|
|
44
42
|
# String representation of the current node, if it is a terminal node.
|
45
43
|
# @return [String] the string representation of the current node.
|
46
|
-
# @raise [
|
44
|
+
# @raise [InvalidOperation] if node is not terminal or is root.
|
47
45
|
def as_word
|
48
|
-
raise InvalidOperation.new
|
46
|
+
raise InvalidOperation.new unless @letter.nil? or terminal?
|
49
47
|
get_letter_string
|
50
48
|
end
|
51
49
|
|
data/lib/rambling-trie/root.rb
CHANGED
@@ -8,50 +8,47 @@ module Rambling
|
|
8
8
|
super(nil)
|
9
9
|
|
10
10
|
@filename = filename
|
11
|
-
@
|
11
|
+
@compressed = false
|
12
12
|
add_all_nodes if filename
|
13
13
|
end
|
14
14
|
|
15
15
|
# Compresses the existing tree using redundant node elimination. Flags the trie as compressed.
|
16
|
-
# @return [
|
16
|
+
# @return [Root] same object
|
17
17
|
def compress!
|
18
|
-
unless compressed?
|
19
|
-
compress_own_tree!
|
20
|
-
@is_compressed = true
|
21
|
-
end
|
22
|
-
|
18
|
+
@compressed = (not compress_tree!.nil?) unless compressed?
|
23
19
|
self
|
24
20
|
end
|
25
21
|
|
26
|
-
# Flag for compressed tries. Overrides {
|
22
|
+
# Flag for compressed tries. Overrides {Compressor#compressed?}.
|
27
23
|
# @return [Boolean] `true` for compressed tries, `false` otherwise.
|
28
24
|
def compressed?
|
29
|
-
@
|
25
|
+
@compressed
|
30
26
|
end
|
31
27
|
|
32
28
|
# Checks if a path for a word or partial word exists in the trie.
|
33
29
|
# @param [String] word the word or partial word to look for in the trie.
|
34
30
|
# @return [Boolean] `true` if the word or partial word is found, `false` otherwise.
|
35
31
|
def has_branch_for?(word = '')
|
36
|
-
|
37
|
-
compressed? ? has_compressed_branch_for?(chars) : has_uncompressed_branch_for?(chars)
|
32
|
+
fulfills_condition word, :has_branch_for?
|
38
33
|
end
|
39
34
|
|
40
35
|
# Checks if a whole word exists in the trie.
|
41
36
|
# @param [String] word the word to look for in the trie.
|
42
37
|
# @return [Boolean] `true` only if the word is found and the last character corresponds to a terminal node.
|
43
38
|
def is_word?(word = '')
|
44
|
-
|
45
|
-
compressed? ? is_compressed_word?(chars) : is_uncompressed_word?(chars)
|
39
|
+
fulfills_condition word, :is_word?
|
46
40
|
end
|
47
41
|
|
42
|
+
alias_method :include?, :is_word?
|
43
|
+
|
48
44
|
private
|
45
|
+
def fulfills_condition(word, method)
|
46
|
+
method = compressed? ? "compressed_#{method}" : "uncompressed_#{method}"
|
47
|
+
send(method, word.chars.to_a)
|
48
|
+
end
|
49
|
+
|
49
50
|
def add_all_nodes
|
50
|
-
File.open(@filename)
|
51
|
-
while word = file.gets
|
52
|
-
add_branch_from(word.chomp)
|
53
|
-
end
|
54
|
-
end
|
51
|
+
File.open(@filename).each_line { |line| self << line.chomp }
|
55
52
|
end
|
56
53
|
end
|
57
54
|
end
|
@@ -9,8 +9,8 @@ namespace :performance do
|
|
9
9
|
methods.each do |method|
|
10
10
|
output.puts "`#{method}`:"
|
11
11
|
words.each do |word|
|
12
|
-
output.print "#{word} - #{trie.send(method, word)}".ljust
|
13
|
-
output.puts Benchmark.measure { 200_000.times {trie.send
|
12
|
+
output.print "#{word} - #{trie.send(method, word)}".ljust 30
|
13
|
+
output.puts Benchmark.measure { 200_000.times { trie.send method, word }}
|
14
14
|
end
|
15
15
|
end
|
16
16
|
end
|
@@ -18,21 +18,29 @@ namespace :performance do
|
|
18
18
|
def generate_report(filename = nil)
|
19
19
|
output = filename.nil? ? $stdout : File.open(filename, 'a+')
|
20
20
|
|
21
|
-
trie = Rambling::Trie.new(get_path('assets', 'dictionaries', 'words_with_friends.txt'))
|
22
|
-
|
23
21
|
output.puts "\nReport for rambling-trie version #{Rambling::Trie::VERSION}"
|
24
|
-
report('Uncompressed', trie, output)
|
25
22
|
|
26
|
-
|
23
|
+
trie = nil
|
24
|
+
measure = Benchmark.measure { trie = Rambling::Trie.create get_path('assets', 'dictionaries', 'words_with_friends.txt') }
|
25
|
+
|
26
|
+
if ENV['profile_creation']
|
27
|
+
output.puts '==> Creation'
|
28
|
+
output.print 'Rambling::Trie.create'.ljust 30
|
29
|
+
output.puts measure
|
30
|
+
end
|
31
|
+
|
32
|
+
report 'Uncompressed', trie, output
|
33
|
+
|
34
|
+
return unless trie.respond_to? :compress!
|
27
35
|
|
28
36
|
trie.compress!
|
29
|
-
report
|
37
|
+
report 'Compressed', trie, output
|
30
38
|
|
31
39
|
output.close
|
32
40
|
end
|
33
41
|
|
34
42
|
def get_path(*filename)
|
35
|
-
File.join
|
43
|
+
File.join File.dirname(__FILE__), '..', '..', '..', *filename
|
36
44
|
end
|
37
45
|
|
38
46
|
desc 'Generate performance report'
|
@@ -45,7 +53,7 @@ namespace :performance do
|
|
45
53
|
desc 'Generate performance report and append result to reports/performance'
|
46
54
|
task :save do
|
47
55
|
puts 'Generating performance report...'
|
48
|
-
generate_report
|
56
|
+
generate_report get_path('reports', 'performance')
|
49
57
|
puts 'Report has been saved to reports/performance'
|
50
58
|
end
|
51
59
|
end
|
@@ -56,7 +64,7 @@ namespace :performance do
|
|
56
64
|
|
57
65
|
puts 'Generating profiling reports...'
|
58
66
|
|
59
|
-
rambling_trie = Rambling::Trie.
|
67
|
+
rambling_trie = Rambling::Trie.create get_path('assets', 'dictionaries', 'words_with_friends.txt')
|
60
68
|
words = ['hi', 'help', 'beautiful', 'impressionism', 'anthropological']
|
61
69
|
methods = [:has_branch_for?, :is_word?]
|
62
70
|
tries = [lambda {rambling_trie.clone}, lambda {rambling_trie.clone.compress!}]
|
@@ -66,12 +74,12 @@ namespace :performance do
|
|
66
74
|
trie = trie_generator.call
|
67
75
|
result = RubyProf.profile do
|
68
76
|
words.each do |word|
|
69
|
-
200_000.times { trie.send
|
77
|
+
200_000.times { trie.send method, word }
|
70
78
|
end
|
71
79
|
end
|
72
80
|
|
73
81
|
File.open get_path('reports', "profile-#{trie.compressed? ? 'compressed' : 'uncompressed'}-#{method.to_s.sub(/\?/, '')}-#{Time.now.to_i}"), 'w' do |file|
|
74
|
-
RubyProf::CallTreePrinter.new(result).print
|
82
|
+
RubyProf::CallTreePrinter.new(result).print file
|
75
83
|
end
|
76
84
|
end
|
77
85
|
end
|
@@ -85,7 +93,7 @@ namespace :performance do
|
|
85
93
|
|
86
94
|
puts 'Generating cpu profiling reports...'
|
87
95
|
|
88
|
-
rambling_trie = Rambling::Trie.
|
96
|
+
rambling_trie = Rambling::Trie.create get_path('assets', 'dictionaries', 'words_with_friends.txt')
|
89
97
|
words = ['hi', 'help', 'beautiful', 'impressionism', 'anthropological']
|
90
98
|
methods = [:has_branch_for?, :is_word?]
|
91
99
|
tries = [lambda {rambling_trie.clone}, lambda {rambling_trie.clone.compress!}]
|
@@ -95,7 +103,7 @@ namespace :performance do
|
|
95
103
|
trie = trie_generator.call
|
96
104
|
result = PerfTools::CpuProfiler.start get_path('reports', "cpu_profile-#{trie.compressed? ? 'compressed' : 'uncompressed'}-#{method.to_s.sub(/\?/, '')}-#{Time.now.to_i}") do
|
97
105
|
words.each do |word|
|
98
|
-
200_000.times { trie.send
|
106
|
+
200_000.times { trie.send method, word }
|
99
107
|
end
|
100
108
|
end
|
101
109
|
end
|
data/rambling-trie.gemspec
CHANGED
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Rambling
|
4
|
+
module Trie
|
5
|
+
describe Branches do
|
6
|
+
describe '#add_branch_from' do
|
7
|
+
context 'new word for existing branch' do
|
8
|
+
let(:node) { Node.new 'back' }
|
9
|
+
|
10
|
+
before :each do
|
11
|
+
node.add_branch_from 'a'
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'does not increment the child count' do
|
15
|
+
node.should have(1).children
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'marks it as terminal' do
|
19
|
+
node[:a].should be_terminal
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
context 'old word for existing branch' do
|
24
|
+
let(:node) { Node.new 'back' }
|
25
|
+
|
26
|
+
before :each do
|
27
|
+
node.add_branch_from 'ack'
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'does not increment any child count' do
|
31
|
+
node.should have(1).children
|
32
|
+
node[:a].should have(1).children
|
33
|
+
node[:a][:c].should have(1).children
|
34
|
+
node[:a][:c][:k].should have(0).children
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe '#<<' do
|
40
|
+
let(:root) { Root.new }
|
41
|
+
let(:word) { 'word' }
|
42
|
+
|
43
|
+
it 'delegates to #add_branch_from' do
|
44
|
+
[true, false].each do |value|
|
45
|
+
root.stub(:add_branch_from).with(word).and_return value
|
46
|
+
root << word
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Rambling
|
4
|
+
module Trie
|
5
|
+
describe ChildrenHashDeferer do
|
6
|
+
class ClassWithChildren
|
7
|
+
include ChildrenHashDeferer
|
8
|
+
attr_accessor :children
|
9
|
+
|
10
|
+
def initialize()
|
11
|
+
@children = {}
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
let(:deferer) { ClassWithChildren.new }
|
16
|
+
|
17
|
+
describe '#[]' do
|
18
|
+
let(:key) { :key }
|
19
|
+
let(:value) { 'value' }
|
20
|
+
|
21
|
+
it 'defers to the children hash' do
|
22
|
+
deferer.children.should_receive(:[]).with(key).and_return value
|
23
|
+
deferer[key].should == value
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe '#[]=' do
|
28
|
+
let(:key) { :key }
|
29
|
+
let(:value) { 'value' }
|
30
|
+
|
31
|
+
it 'defers to the children hash' do
|
32
|
+
deferer.children.should_receive(:[]=).with(key, value)
|
33
|
+
deferer[key] = value
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe '#delete' do
|
38
|
+
let(:key) { :key }
|
39
|
+
let(:value) { 'value' }
|
40
|
+
|
41
|
+
it 'defers to the children hash' do
|
42
|
+
deferer.children.should_receive(:delete).with(key).and_return value
|
43
|
+
deferer.delete(key).should == value
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe '#has_key' do
|
48
|
+
let(:key) { :key }
|
49
|
+
|
50
|
+
it 'defers to the children hash' do
|
51
|
+
[true, false].each do |value|
|
52
|
+
deferer.children.should_receive(:has_key?).with(key).and_return value
|
53
|
+
deferer.has_key?(key).should send("be_#{value}")
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -95,39 +95,6 @@ module Rambling
|
|
95
95
|
end
|
96
96
|
end
|
97
97
|
|
98
|
-
describe '#add_branch_from' do
|
99
|
-
context 'new word for existing branch' do
|
100
|
-
let(:node) { Node.new 'back' }
|
101
|
-
|
102
|
-
before :each do
|
103
|
-
node.add_branch_from 'a'
|
104
|
-
end
|
105
|
-
|
106
|
-
it 'does not increment the child count' do
|
107
|
-
node.should have(1).children
|
108
|
-
end
|
109
|
-
|
110
|
-
it 'marks it as terminal' do
|
111
|
-
node[:a].should be_terminal
|
112
|
-
end
|
113
|
-
end
|
114
|
-
|
115
|
-
context 'old word for existing branch' do
|
116
|
-
let(:node) { Node.new 'back' }
|
117
|
-
|
118
|
-
before :each do
|
119
|
-
node.add_branch_from 'ack'
|
120
|
-
end
|
121
|
-
|
122
|
-
it 'does not increment any child count' do
|
123
|
-
node.should have(1).children
|
124
|
-
node[:a].should have(1).children
|
125
|
-
node[:a][:c].should have(1).children
|
126
|
-
node[:a][:c][:k].should have(0).children
|
127
|
-
end
|
128
|
-
end
|
129
|
-
end
|
130
|
-
|
131
98
|
describe '#as_word' do
|
132
99
|
context 'for an empty node' do
|
133
100
|
let(:node) { Node.new '' }
|
@@ -174,7 +141,7 @@ module Rambling
|
|
174
141
|
end
|
175
142
|
|
176
143
|
describe '#compressed?' do
|
177
|
-
let(:root) { double
|
144
|
+
let(:root) { double 'Root' }
|
178
145
|
let(:node) { Node.new '', root }
|
179
146
|
|
180
147
|
context 'parent is compressed' do
|
@@ -37,7 +37,7 @@ module Rambling
|
|
37
37
|
end
|
38
38
|
|
39
39
|
it 'loads every word' do
|
40
|
-
File.open
|
40
|
+
File.open filename do |file|
|
41
41
|
file.readlines.each { |word| root.is_word?(word.chomp).should be_true }
|
42
42
|
end
|
43
43
|
end
|
@@ -86,7 +86,7 @@ module Rambling
|
|
86
86
|
|
87
87
|
context 'with at least one word' do
|
88
88
|
it 'keeps the root letter nil' do
|
89
|
-
root.add_branch_from
|
89
|
+
root.add_branch_from 'all'
|
90
90
|
root.compress!
|
91
91
|
|
92
92
|
root.letter.should be_nil
|
@@ -95,7 +95,7 @@ module Rambling
|
|
95
95
|
|
96
96
|
context 'with a single word' do
|
97
97
|
before :each do
|
98
|
-
root.add_branch_from
|
98
|
+
root.add_branch_from 'all'
|
99
99
|
root.compress!
|
100
100
|
end
|
101
101
|
|
@@ -109,8 +109,8 @@ module Rambling
|
|
109
109
|
|
110
110
|
context 'with two words' do
|
111
111
|
before :each do
|
112
|
-
root.add_branch_from
|
113
|
-
root.add_branch_from
|
112
|
+
root.add_branch_from 'all'
|
113
|
+
root.add_branch_from 'ask'
|
114
114
|
root.compress!
|
115
115
|
end
|
116
116
|
|
@@ -133,9 +133,9 @@ module Rambling
|
|
133
133
|
end
|
134
134
|
|
135
135
|
it 'reassigns the parent nodes correctly' do
|
136
|
-
root.add_branch_from
|
137
|
-
root.add_branch_from
|
138
|
-
root.add_branch_from
|
136
|
+
root.add_branch_from 'repay'
|
137
|
+
root.add_branch_from 'rest'
|
138
|
+
root.add_branch_from 'repaint'
|
139
139
|
root.compress!
|
140
140
|
|
141
141
|
root[:re].letter.should == :re
|
@@ -158,9 +158,9 @@ module Rambling
|
|
158
158
|
end
|
159
159
|
|
160
160
|
it 'does not compress terminal nodes' do
|
161
|
-
root.add_branch_from
|
162
|
-
root.add_branch_from
|
163
|
-
root.add_branch_from
|
161
|
+
root.add_branch_from 'you'
|
162
|
+
root.add_branch_from 'your'
|
163
|
+
root.add_branch_from 'yours'
|
164
164
|
|
165
165
|
root.compress!
|
166
166
|
|
@@ -175,9 +175,9 @@ module Rambling
|
|
175
175
|
|
176
176
|
describe 'and trying to add a branch' do
|
177
177
|
it 'raises an error' do
|
178
|
-
root.add_branch_from
|
179
|
-
root.add_branch_from
|
180
|
-
root.add_branch_from
|
178
|
+
root.add_branch_from 'repay'
|
179
|
+
root.add_branch_from 'rest'
|
180
|
+
root.add_branch_from 'repaint'
|
181
181
|
root.compress!
|
182
182
|
|
183
183
|
lambda { root.add_branch_from('restaurant') }.should raise_error(InvalidOperation)
|
@@ -191,8 +191,8 @@ module Rambling
|
|
191
191
|
context 'word is contained' do
|
192
192
|
shared_examples_for 'word is found' do
|
193
193
|
it 'matches part of the word' do
|
194
|
-
root.should have_branch_for
|
195
|
-
root.should have_branch_for
|
194
|
+
root.should have_branch_for 'hell'
|
195
|
+
root.should have_branch_for 'hig'
|
196
196
|
end
|
197
197
|
|
198
198
|
it 'matches the whole word' do
|
@@ -220,8 +220,8 @@ module Rambling
|
|
220
220
|
context 'word is not contained' do
|
221
221
|
shared_examples_for 'word not found' do
|
222
222
|
it 'does not match any part of the word' do
|
223
|
-
root.should_not have_branch_for
|
224
|
-
root.should_not have_branch_for
|
223
|
+
root.should_not have_branch_for 'ha'
|
224
|
+
root.should_not have_branch_for 'hal'
|
225
225
|
end
|
226
226
|
|
227
227
|
it 'does not match the whole word' do
|
@@ -244,6 +244,18 @@ module Rambling
|
|
244
244
|
end
|
245
245
|
end
|
246
246
|
end
|
247
|
+
|
248
|
+
describe '#include?' do
|
249
|
+
let(:root) { Root.new }
|
250
|
+
let(:word) { 'word' }
|
251
|
+
|
252
|
+
it 'delegates to #is_word?' do
|
253
|
+
[true, false].each do |value|
|
254
|
+
root.stub(:is_word?).with(word).and_return value
|
255
|
+
root.include?(word).should &method("be_#{value}".to_sym)
|
256
|
+
end
|
257
|
+
end
|
258
|
+
end
|
247
259
|
end
|
248
260
|
end
|
249
261
|
end
|
@@ -3,7 +3,7 @@ require 'spec_helper'
|
|
3
3
|
module Rambling
|
4
4
|
describe Trie do
|
5
5
|
describe '.create' do
|
6
|
-
let(:root) { double
|
6
|
+
let(:root) { double 'Trie::Root' }
|
7
7
|
|
8
8
|
before :each do
|
9
9
|
Trie::Root.stub(:new).and_return root
|
@@ -13,5 +13,22 @@ module Rambling
|
|
13
13
|
Trie.create.should == root
|
14
14
|
end
|
15
15
|
end
|
16
|
+
|
17
|
+
describe '.new' do
|
18
|
+
let(:root) { double 'Trie::Root' }
|
19
|
+
|
20
|
+
before :each do
|
21
|
+
Trie.should_receive(:create).and_return root
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'returns the new trie root node instance' do
|
25
|
+
Trie.new.should == root
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'warns about deprecation' do
|
29
|
+
Trie.should_receive(:warn).with '[DEPRECATION] `new` is deprecated. Please use `create` instead.'
|
30
|
+
Trie.new
|
31
|
+
end
|
32
|
+
end
|
16
33
|
end
|
17
34
|
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.4.
|
4
|
+
version: 0.4.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-07-
|
12
|
+
date: 2012-07-20 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rspec
|
@@ -118,10 +118,11 @@ files:
|
|
118
118
|
- lib/rambling-trie/tasks/gem.rb
|
119
119
|
- lib/rambling-trie/tasks/performance.rb
|
120
120
|
- lib/rambling-trie/version.rb
|
121
|
-
- lib/rambling.rb
|
122
121
|
- rambling-trie.gemspec
|
123
122
|
- reports/performance
|
124
123
|
- spec/assets/test_words.txt
|
124
|
+
- spec/lib/rambling-trie/branches_spec.rb
|
125
|
+
- spec/lib/rambling-trie/children_hash_deferer_spec.rb
|
125
126
|
- spec/lib/rambling-trie/node_spec.rb
|
126
127
|
- spec/lib/rambling-trie/root_spec.rb
|
127
128
|
- spec/lib/rambling-trie_spec.rb
|
@@ -140,7 +141,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
140
141
|
version: '0'
|
141
142
|
segments:
|
142
143
|
- 0
|
143
|
-
hash:
|
144
|
+
hash: 1932693806363009255
|
144
145
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
145
146
|
none: false
|
146
147
|
requirements:
|
@@ -149,7 +150,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
149
150
|
version: '0'
|
150
151
|
segments:
|
151
152
|
- 0
|
152
|
-
hash:
|
153
|
+
hash: 1932693806363009255
|
153
154
|
requirements: []
|
154
155
|
rubyforge_project:
|
155
156
|
rubygems_version: 1.8.24
|
data/lib/rambling.rb
DELETED