rambling-trie 1.0.2 → 1.0.3
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.
- checksums.yaml +4 -4
- data/Gemfile +2 -1
- data/README.md +23 -7
- data/Rakefile +4 -0
- data/lib/rambling/trie.rb +27 -21
- data/lib/rambling/trie/comparable.rb +3 -3
- data/lib/rambling/trie/compressible.rb +14 -0
- data/lib/rambling/trie/compressor.rb +37 -24
- data/lib/rambling/trie/configuration/properties.rb +8 -6
- data/lib/rambling/trie/configuration/provider_collection.rb +34 -16
- data/lib/rambling/trie/container.rb +156 -36
- data/lib/rambling/trie/enumerable.rb +4 -4
- data/lib/rambling/trie/nodes.rb +11 -0
- data/lib/rambling/trie/nodes/compressed.rb +115 -0
- data/lib/rambling/trie/nodes/missing.rb +10 -0
- data/lib/rambling/trie/nodes/node.rb +151 -0
- data/lib/rambling/trie/nodes/raw.rb +89 -0
- data/lib/rambling/trie/readers/plain_text.rb +1 -11
- data/lib/rambling/trie/serializers/marshal.rb +4 -4
- data/lib/rambling/trie/serializers/yaml.rb +4 -4
- data/lib/rambling/trie/serializers/zip.rb +9 -8
- data/lib/rambling/trie/version.rb +1 -1
- data/spec/assets/test_words.es_DO.txt +1 -0
- data/spec/integration/rambling/trie_spec.rb +40 -35
- data/spec/lib/rambling/trie/comparable_spec.rb +6 -15
- data/spec/lib/rambling/trie/compressor_spec.rb +88 -13
- data/spec/lib/rambling/trie/configuration/properties_spec.rb +7 -7
- data/spec/lib/rambling/trie/configuration/provider_collection_spec.rb +8 -20
- data/spec/lib/rambling/trie/container_spec.rb +159 -168
- data/spec/lib/rambling/trie/enumerable_spec.rb +12 -9
- data/spec/lib/rambling/trie/inspectable_spec.rb +11 -11
- data/spec/lib/rambling/trie/nodes/compressed_spec.rb +35 -0
- data/spec/lib/rambling/trie/nodes/node_spec.rb +7 -0
- data/spec/lib/rambling/trie/nodes/raw_spec.rb +177 -0
- data/spec/lib/rambling/trie/serializers/file_spec.rb +4 -4
- data/spec/lib/rambling/trie/serializers/marshal_spec.rb +3 -7
- data/spec/lib/rambling/trie/serializers/yaml_spec.rb +3 -7
- data/spec/lib/rambling/trie/serializers/zip_spec.rb +16 -20
- data/spec/lib/rambling/trie/stringifyable_spec.rb +7 -8
- data/spec/lib/rambling/trie_spec.rb +2 -2
- data/spec/spec_helper.rb +3 -1
- data/spec/support/config.rb +4 -0
- data/spec/support/helpers/add_word.rb +18 -0
- data/spec/support/shared_examples/{a_compressable_trie.rb → a_compressible_trie.rb} +13 -3
- data/spec/support/shared_examples/a_serializable_trie.rb +8 -6
- data/spec/support/shared_examples/a_serializer.rb +6 -0
- data/spec/{lib/rambling/trie/node_spec.rb → support/shared_examples/a_trie_node.rb} +61 -30
- data/spec/{lib/rambling/trie/compressed_node_spec.rb → support/shared_examples/a_trie_node_implementation.rb} +18 -69
- metadata +22 -15
- data/lib/rambling/trie/compressable.rb +0 -14
- data/lib/rambling/trie/compressed_node.rb +0 -120
- data/lib/rambling/trie/missing_node.rb +0 -8
- data/lib/rambling/trie/node.rb +0 -97
- data/lib/rambling/trie/raw_node.rb +0 -96
- data/spec/lib/rambling/trie/raw_node_spec.rb +0 -389
@@ -1,14 +0,0 @@
|
|
1
|
-
module Rambling
|
2
|
-
module Trie
|
3
|
-
# Provides the compressable behavior for the trie data structure.
|
4
|
-
module Compressable
|
5
|
-
# Indicates if the current {Rambling::Trie::Node Node} can be compressed
|
6
|
-
# or not.
|
7
|
-
# @return [Boolean] `true` for non-{Node#terminal? terminal} nodes with
|
8
|
-
# one child, `false` otherwise.
|
9
|
-
def compressable?
|
10
|
-
!(root? || terminal?) && children_tree.size == 1
|
11
|
-
end
|
12
|
-
end
|
13
|
-
end
|
14
|
-
end
|
@@ -1,120 +0,0 @@
|
|
1
|
-
module Rambling
|
2
|
-
module Trie
|
3
|
-
# A representation of a node in an compressed trie data structure.
|
4
|
-
class CompressedNode < Rambling::Trie::Node
|
5
|
-
# Always raises {Rambling::Trie::InvalidOperation InvalidOperation} when
|
6
|
-
# trying to add a word to the current compressed trie node
|
7
|
-
# @param [String] word the word to add to the trie.
|
8
|
-
# @raise [InvalidOperation] if the trie is already compressed.
|
9
|
-
# @return [nil] this never returns as it always raises an exception.
|
10
|
-
def add word
|
11
|
-
raise Rambling::Trie::InvalidOperation, 'Cannot add word to compressed trie'
|
12
|
-
end
|
13
|
-
|
14
|
-
# Checks if a path for set a of characters exists in the trie.
|
15
|
-
# @param [Array<String>] chars the characters to look for in the trie.
|
16
|
-
# @return [Boolean] `true` if the characters are found, `false` otherwise.
|
17
|
-
def partial_word? chars
|
18
|
-
chars.empty? || has_partial_word?(chars)
|
19
|
-
end
|
20
|
-
|
21
|
-
# Checks if a path for set of characters represents a word in the trie.
|
22
|
-
# @param [Array<String>] chars the characters to look for in the trie.
|
23
|
-
# @return [Boolean] `true` if the characters are found and form a word,
|
24
|
-
# `false` otherwise.
|
25
|
-
def word? chars
|
26
|
-
chars.empty? ? terminal? : has_word?(chars)
|
27
|
-
end
|
28
|
-
|
29
|
-
# Returns the node that starts with the specified characters.
|
30
|
-
# @param [Array<String>] chars the characters to look for in the trie.
|
31
|
-
# @return [Node] the node that matches the specified characters.
|
32
|
-
# {MissingNode MissingNode} when not found.
|
33
|
-
def scan chars
|
34
|
-
chars.empty? ? self : closest_node(chars)
|
35
|
-
end
|
36
|
-
|
37
|
-
# Always return `true` for a compressed node.
|
38
|
-
# @return [Boolean] always `true` for a compressed node.
|
39
|
-
def compressed?
|
40
|
-
true
|
41
|
-
end
|
42
|
-
|
43
|
-
private
|
44
|
-
|
45
|
-
def has_partial_word? chars
|
46
|
-
recursive_get(:partial_word?, chars) || false
|
47
|
-
end
|
48
|
-
|
49
|
-
def has_word? chars
|
50
|
-
current_key = nil
|
51
|
-
|
52
|
-
while !chars.empty?
|
53
|
-
if current_key
|
54
|
-
current_key << chars.slice!(0)
|
55
|
-
else
|
56
|
-
current_key = chars.slice!(0)
|
57
|
-
end
|
58
|
-
|
59
|
-
child = children_tree[current_key.to_sym]
|
60
|
-
return child.word? chars if child
|
61
|
-
end
|
62
|
-
|
63
|
-
false
|
64
|
-
end
|
65
|
-
|
66
|
-
def closest_node chars
|
67
|
-
recursive_get(:scan, chars) || Rambling::Trie::MissingNode.new
|
68
|
-
end
|
69
|
-
|
70
|
-
def children_match_prefix chars
|
71
|
-
return enum_for :children_match_prefix, chars unless block_given?
|
72
|
-
|
73
|
-
current_key = nil
|
74
|
-
|
75
|
-
while !chars.empty?
|
76
|
-
if current_key
|
77
|
-
current_key << chars.slice!(0)
|
78
|
-
else
|
79
|
-
current_key = chars.slice!(0)
|
80
|
-
end
|
81
|
-
|
82
|
-
child = children_tree[current_key.to_sym]
|
83
|
-
|
84
|
-
if child
|
85
|
-
child.match_prefix chars do |word|
|
86
|
-
yield word
|
87
|
-
end
|
88
|
-
end
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
92
|
-
def recursive_get method, chars
|
93
|
-
current_length = 0
|
94
|
-
current_key = current_key chars.slice!(0)
|
95
|
-
|
96
|
-
begin
|
97
|
-
current_length += 1
|
98
|
-
|
99
|
-
if current_key && (current_key.length == current_length || chars.empty?)
|
100
|
-
return children_tree[current_key.to_sym].send method, chars
|
101
|
-
end
|
102
|
-
end while current_key && current_key[current_length] == chars.slice!(0)
|
103
|
-
end
|
104
|
-
|
105
|
-
def current_key letter
|
106
|
-
current_key = nil
|
107
|
-
|
108
|
-
children_tree.keys.each do |key|
|
109
|
-
key_string = key.to_s
|
110
|
-
if key_string.start_with? letter
|
111
|
-
current_key = key_string
|
112
|
-
break
|
113
|
-
end
|
114
|
-
end
|
115
|
-
|
116
|
-
current_key
|
117
|
-
end
|
118
|
-
end
|
119
|
-
end
|
120
|
-
end
|
data/lib/rambling/trie/node.rb
DELETED
@@ -1,97 +0,0 @@
|
|
1
|
-
module Rambling
|
2
|
-
module Trie
|
3
|
-
# A representation of a node in the trie data structure.
|
4
|
-
class Node
|
5
|
-
extend ::Forwardable
|
6
|
-
include Rambling::Trie::Compressable
|
7
|
-
include Rambling::Trie::Enumerable
|
8
|
-
include Rambling::Trie::Comparable
|
9
|
-
include Rambling::Trie::Stringifyable
|
10
|
-
include Rambling::Trie::Inspectable
|
11
|
-
|
12
|
-
delegate [
|
13
|
-
:[],
|
14
|
-
:[]=,
|
15
|
-
:delete,
|
16
|
-
:has_key?
|
17
|
-
] => :children_tree
|
18
|
-
|
19
|
-
# @overload letter
|
20
|
-
# Letter(s) corresponding to the current node.
|
21
|
-
# @overload letter=(letter)
|
22
|
-
# Sets the letter(s) corresponding to the current node. Ensures the
|
23
|
-
# {Node#letter #letter} in the {Node#parent #parent}'s
|
24
|
-
# {Node#children_tree #children_tree} is updated.
|
25
|
-
# @param [String, Symbol, nil] letter the new letter value.
|
26
|
-
# @return [Symbol, nil] the corresponding letter(s).
|
27
|
-
attr_reader :letter
|
28
|
-
|
29
|
-
# Children nodes tree.
|
30
|
-
# @return [Hash] the children_tree hash, consisting of `:letter => node`.
|
31
|
-
attr_accessor :children_tree
|
32
|
-
|
33
|
-
# Parent node.
|
34
|
-
# @return [Node, nil] the parent of the current node.
|
35
|
-
attr_accessor :parent
|
36
|
-
|
37
|
-
# Creates a new node.
|
38
|
-
# @param [Node, nil] parent the parent of the current node.
|
39
|
-
def initialize parent = nil
|
40
|
-
self.parent = parent
|
41
|
-
self.children_tree = {}
|
42
|
-
end
|
43
|
-
|
44
|
-
# Children nodes.
|
45
|
-
# @return [Array<Node>] the array of children nodes contained in the
|
46
|
-
# current node.
|
47
|
-
def children
|
48
|
-
children_tree.values
|
49
|
-
end
|
50
|
-
|
51
|
-
# Indicates if the current node is the root node.
|
52
|
-
# @return [Boolean] `true` if the node does not have a parent, `false`
|
53
|
-
# otherwise.
|
54
|
-
def root?
|
55
|
-
!parent
|
56
|
-
end
|
57
|
-
|
58
|
-
# Indicates if a {Node Node} is terminal or not.
|
59
|
-
# @return [Boolean] `true` for terminal nodes, `false` otherwise.
|
60
|
-
def terminal?
|
61
|
-
!!terminal
|
62
|
-
end
|
63
|
-
|
64
|
-
# Mark {Node Node} as terminal.
|
65
|
-
# @return [Node] the modified node.
|
66
|
-
def terminal!
|
67
|
-
self.terminal = true
|
68
|
-
self
|
69
|
-
end
|
70
|
-
|
71
|
-
def letter= letter
|
72
|
-
if letter
|
73
|
-
@letter = letter.to_sym
|
74
|
-
parent[letter] = self if parent
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
|
-
# Returns all words that match a prefix of any length within chars.
|
79
|
-
# @param [String] chars the chars to base the prefix on.
|
80
|
-
# @return [Enumerator<String>] all the words that match a prefix given by
|
81
|
-
# chars.
|
82
|
-
# @yield [String] each word found.
|
83
|
-
def match_prefix chars
|
84
|
-
return enum_for :match_prefix, chars unless block_given?
|
85
|
-
|
86
|
-
yield as_word if terminal?
|
87
|
-
children_match_prefix chars do |word|
|
88
|
-
yield word
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
92
|
-
private
|
93
|
-
|
94
|
-
attr_accessor :terminal
|
95
|
-
end
|
96
|
-
end
|
97
|
-
end
|
@@ -1,96 +0,0 @@
|
|
1
|
-
module Rambling
|
2
|
-
module Trie
|
3
|
-
# A representation of a node in an uncompressed trie data structure.
|
4
|
-
class RawNode < Rambling::Trie::Node
|
5
|
-
# Adds a word to the current raw (uncompressed) trie node.
|
6
|
-
# @param [String] word the word to add to the trie.
|
7
|
-
# @return [RawNode] the added/modified node based on the word added.
|
8
|
-
# @note This method clears the contents of the word variable.
|
9
|
-
def add word
|
10
|
-
if word.empty?
|
11
|
-
terminal!
|
12
|
-
else
|
13
|
-
add_to_children_tree word
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
# Checks if a path for a set of characters exists in the trie.
|
18
|
-
# @param [Array<String>] chars the characters to look for in the trie.
|
19
|
-
# @return [Boolean] `true` if the characters are found, `false`
|
20
|
-
# otherwise.
|
21
|
-
def partial_word? chars = []
|
22
|
-
if chars.empty?
|
23
|
-
true
|
24
|
-
else
|
25
|
-
letter = chars.slice!(0).to_sym
|
26
|
-
child = children_tree[letter]
|
27
|
-
!!child && child.partial_word?(chars)
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
# Checks if a path for set of characters represents a word in the trie.
|
32
|
-
# @param [Array<String>] chars the characters to look for in the trie.
|
33
|
-
# @return [Boolean] `true` if the characters are found and form a word,
|
34
|
-
# `false` otherwise.
|
35
|
-
def word? chars = []
|
36
|
-
if chars.empty?
|
37
|
-
terminal?
|
38
|
-
else
|
39
|
-
letter = chars.slice!(0).to_sym
|
40
|
-
child = children_tree[letter]
|
41
|
-
!!child && child.word?(chars)
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
# Returns the node that starts with the specified characters.
|
46
|
-
# @param [Array<String>] chars the characters to look for in the trie.
|
47
|
-
# @return [Node] the node that matches the specified characters.
|
48
|
-
# {MissingNode MissingNode} when not found.
|
49
|
-
def scan chars
|
50
|
-
chars.empty? ? self : closest_node(chars)
|
51
|
-
end
|
52
|
-
|
53
|
-
# Always return `false` for a raw (uncompressed) node.
|
54
|
-
# @return [Boolean] always `false` for a raw (uncompressed) node.
|
55
|
-
def compressed?
|
56
|
-
false
|
57
|
-
end
|
58
|
-
|
59
|
-
private
|
60
|
-
|
61
|
-
def add_to_children_tree word
|
62
|
-
letter = word.slice!(0).to_sym
|
63
|
-
child = children_tree[letter] || new_node(letter)
|
64
|
-
child.add word
|
65
|
-
child
|
66
|
-
end
|
67
|
-
|
68
|
-
def new_node letter
|
69
|
-
node = Rambling::Trie::RawNode.new self
|
70
|
-
node.letter = letter
|
71
|
-
children_tree[letter] = node
|
72
|
-
end
|
73
|
-
|
74
|
-
def closest_node chars
|
75
|
-
letter = chars.slice!(0).to_sym
|
76
|
-
child = children_tree[letter]
|
77
|
-
|
78
|
-
child ? child.scan(chars) : Rambling::Trie::MissingNode.new
|
79
|
-
end
|
80
|
-
|
81
|
-
def children_match_prefix chars
|
82
|
-
return enum_for :children_match_prefix, chars unless block_given?
|
83
|
-
|
84
|
-
if !chars.empty?
|
85
|
-
letter = chars.slice!(0).to_sym
|
86
|
-
child = children_tree[letter]
|
87
|
-
if child
|
88
|
-
child.match_prefix chars do |word|
|
89
|
-
yield word
|
90
|
-
end
|
91
|
-
end
|
92
|
-
end
|
93
|
-
end
|
94
|
-
end
|
95
|
-
end
|
96
|
-
end
|
@@ -1,389 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
describe Rambling::Trie::RawNode do
|
4
|
-
let(:node) { Rambling::Trie::RawNode.new }
|
5
|
-
|
6
|
-
describe '#compressed?' do
|
7
|
-
it 'returns false' do
|
8
|
-
expect(node).not_to be_compressed
|
9
|
-
end
|
10
|
-
end
|
11
|
-
|
12
|
-
describe '.new' do
|
13
|
-
context 'with no word' do
|
14
|
-
let(:node) { Rambling::Trie::RawNode.new }
|
15
|
-
|
16
|
-
it 'does not have any letter' do
|
17
|
-
expect(node.letter).to be_nil
|
18
|
-
end
|
19
|
-
|
20
|
-
it 'includes no children' do
|
21
|
-
expect(node.children.size).to eq 0
|
22
|
-
end
|
23
|
-
|
24
|
-
it 'is not a terminal node' do
|
25
|
-
expect(node).not_to be_terminal
|
26
|
-
end
|
27
|
-
|
28
|
-
it 'returns empty string as its word' do
|
29
|
-
expect(node.as_word).to be_empty
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
describe '#letter=' do
|
34
|
-
let(:parent) { Rambling::Trie::RawNode.new }
|
35
|
-
let(:node) { Rambling::Trie::RawNode.new parent }
|
36
|
-
|
37
|
-
context 'with empty string' do
|
38
|
-
before do
|
39
|
-
node.letter = :a
|
40
|
-
node.add ''
|
41
|
-
end
|
42
|
-
|
43
|
-
it 'makes it the node letter' do
|
44
|
-
expect(node.letter).to eq :a
|
45
|
-
end
|
46
|
-
|
47
|
-
it 'includes no children' do
|
48
|
-
expect(node.children.size).to eq 0
|
49
|
-
end
|
50
|
-
|
51
|
-
it 'is a terminal node' do
|
52
|
-
expect(node).to be_terminal
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
context 'with one letter' do
|
57
|
-
before do
|
58
|
-
node.letter = :b
|
59
|
-
node.add 'a'
|
60
|
-
end
|
61
|
-
|
62
|
-
it 'takes the first as the node letter' do
|
63
|
-
expect(node.letter).to eq :b
|
64
|
-
end
|
65
|
-
|
66
|
-
it 'includes one child' do
|
67
|
-
expect(node.children.size).to eq 1
|
68
|
-
end
|
69
|
-
|
70
|
-
it 'includes a child with the expected letter' do
|
71
|
-
expect(node.children.first.letter).to eq :a
|
72
|
-
end
|
73
|
-
|
74
|
-
it 'has the expected letter as a key' do
|
75
|
-
expect(node).to have_key(:a)
|
76
|
-
end
|
77
|
-
|
78
|
-
it 'returns the child corresponding to the key' do
|
79
|
-
expect(node[:a]).to eq node.children_tree[:a]
|
80
|
-
end
|
81
|
-
|
82
|
-
it 'does not mark itself as a terminal node' do
|
83
|
-
expect(node).not_to be_terminal
|
84
|
-
end
|
85
|
-
|
86
|
-
it 'marks the first child as a terminal node' do
|
87
|
-
expect(node[:a]).to be_terminal
|
88
|
-
end
|
89
|
-
end
|
90
|
-
|
91
|
-
context 'with a large word' do
|
92
|
-
before do
|
93
|
-
node.letter = :s
|
94
|
-
node.add 'paghetti'
|
95
|
-
end
|
96
|
-
|
97
|
-
it 'marks the last letter as terminal node' do
|
98
|
-
expect(node[:p][:a][:g][:h][:e][:t][:t][:i]).to be_terminal
|
99
|
-
end
|
100
|
-
|
101
|
-
it 'does not mark any other letter as terminal node' do
|
102
|
-
expect(node[:p][:a][:g][:h][:e][:t][:t]).not_to be_terminal
|
103
|
-
expect(node[:p][:a][:g][:h][:e][:t]).not_to be_terminal
|
104
|
-
expect(node[:p][:a][:g][:h][:e]).not_to be_terminal
|
105
|
-
expect(node[:p][:a][:g][:h]).not_to be_terminal
|
106
|
-
expect(node[:p][:a][:g]).not_to be_terminal
|
107
|
-
expect(node[:p][:a]).not_to be_terminal
|
108
|
-
expect(node[:p]).not_to be_terminal
|
109
|
-
end
|
110
|
-
end
|
111
|
-
|
112
|
-
context 'with no parent' do
|
113
|
-
before do
|
114
|
-
node.letter = :a
|
115
|
-
node.add ''
|
116
|
-
end
|
117
|
-
|
118
|
-
it 'makes it the node letter' do
|
119
|
-
expect(node.letter).to eq :a
|
120
|
-
end
|
121
|
-
|
122
|
-
it 'includes no children' do
|
123
|
-
expect(node.children.size).to eq 0
|
124
|
-
end
|
125
|
-
|
126
|
-
it 'is a terminal node' do
|
127
|
-
expect(node).to be_terminal
|
128
|
-
end
|
129
|
-
end
|
130
|
-
end
|
131
|
-
|
132
|
-
context 'with no parent' do
|
133
|
-
let(:node) { Rambling::Trie::RawNode.new }
|
134
|
-
|
135
|
-
it 'is marked as root' do
|
136
|
-
expect(node).to be_root
|
137
|
-
end
|
138
|
-
end
|
139
|
-
|
140
|
-
context 'with a specified parent' do
|
141
|
-
let(:node) { Rambling::Trie::RawNode.new double(:root) }
|
142
|
-
|
143
|
-
it 'is not marked as root' do
|
144
|
-
expect(node).not_to be_root
|
145
|
-
end
|
146
|
-
end
|
147
|
-
|
148
|
-
it 'has no children' do
|
149
|
-
expect(node.children.size).to eq 0
|
150
|
-
end
|
151
|
-
|
152
|
-
it 'has no letter' do
|
153
|
-
expect(node.letter).to be_nil
|
154
|
-
end
|
155
|
-
|
156
|
-
it 'is not a terminal node' do
|
157
|
-
expect(node).not_to be_terminal
|
158
|
-
end
|
159
|
-
|
160
|
-
it 'is not a word' do
|
161
|
-
expect(node).not_to be_word
|
162
|
-
end
|
163
|
-
end
|
164
|
-
|
165
|
-
describe '#add' do
|
166
|
-
context 'when the node has no branches' do
|
167
|
-
before do
|
168
|
-
node.add 'abc'
|
169
|
-
end
|
170
|
-
|
171
|
-
it 'adds only one child' do
|
172
|
-
expect(node.children.size).to eq 1
|
173
|
-
end
|
174
|
-
|
175
|
-
it 'adds the full subtree' do
|
176
|
-
expect(node[:a]).not_to be_nil
|
177
|
-
expect(node[:a][:b]).not_to be_nil
|
178
|
-
expect(node[:a][:b][:c]).not_to be_nil
|
179
|
-
end
|
180
|
-
|
181
|
-
it 'marks only the last child as terminal' do
|
182
|
-
expect(node).not_to be_terminal
|
183
|
-
expect(node[:a]).not_to be_terminal
|
184
|
-
expect(node[:a][:b]).not_to be_terminal
|
185
|
-
expect(node[:a][:b][:c]).to be_terminal
|
186
|
-
end
|
187
|
-
end
|
188
|
-
|
189
|
-
context 'when the word being added already exists in the node' do
|
190
|
-
before do
|
191
|
-
node.add 'ack'
|
192
|
-
end
|
193
|
-
|
194
|
-
it 'does not increment any child count in the tree' do
|
195
|
-
node.add 'ack'
|
196
|
-
|
197
|
-
expect(node.children.size).to eq 1
|
198
|
-
expect(node[:a].children.size).to eq 1
|
199
|
-
expect(node[:a][:c].children.size).to eq 1
|
200
|
-
expect(node[:a][:c][:k].children.size).to eq 0
|
201
|
-
end
|
202
|
-
|
203
|
-
it 'does not mark any child as terminal in the tree' do
|
204
|
-
node.add 'ack'
|
205
|
-
|
206
|
-
expect(node).not_to be_terminal
|
207
|
-
expect(node[:a]).not_to be_terminal
|
208
|
-
expect(node[:a][:c]).not_to be_terminal
|
209
|
-
expect(node[:a][:c][:k]).to be_terminal
|
210
|
-
end
|
211
|
-
|
212
|
-
it 'returns the added node' do
|
213
|
-
expect(node.add('ack').letter).to eq :a
|
214
|
-
end
|
215
|
-
end
|
216
|
-
|
217
|
-
context 'when the word does not exist in the tree but the letters do' do
|
218
|
-
before do
|
219
|
-
node.add 'ack'
|
220
|
-
end
|
221
|
-
|
222
|
-
it 'does not add another branch' do
|
223
|
-
node.add 'a'
|
224
|
-
expect(node.children.size).to eq 1
|
225
|
-
end
|
226
|
-
|
227
|
-
it 'marks the corresponding node as terminal' do
|
228
|
-
node.add 'a'
|
229
|
-
|
230
|
-
expect(node).not_to be_terminal
|
231
|
-
expect(node[:a]).to be_terminal
|
232
|
-
expect(node[:a][:c]).not_to be_terminal
|
233
|
-
expect(node[:a][:c][:k]).to be_terminal
|
234
|
-
end
|
235
|
-
|
236
|
-
it 'returns the added node' do
|
237
|
-
expect(node.add('a').letter).to eq :a
|
238
|
-
end
|
239
|
-
end
|
240
|
-
end
|
241
|
-
|
242
|
-
describe '#partial_word?' do
|
243
|
-
context 'when the chars array is empty' do
|
244
|
-
it 'returns true' do
|
245
|
-
expect(node.partial_word? []).to be true
|
246
|
-
end
|
247
|
-
end
|
248
|
-
|
249
|
-
context 'when the chars array is not empty' do
|
250
|
-
context 'when the node has a tree that matches the characters' do
|
251
|
-
before do
|
252
|
-
node.add 'abc'
|
253
|
-
end
|
254
|
-
|
255
|
-
it 'returns true' do
|
256
|
-
expect(node.partial_word? %w(a)).to be true
|
257
|
-
expect(node.partial_word? %w(a b)).to be true
|
258
|
-
expect(node.partial_word? %w(a b c)).to be true
|
259
|
-
end
|
260
|
-
end
|
261
|
-
|
262
|
-
context 'when the node has a tree that does not match the characters' do
|
263
|
-
before do
|
264
|
-
node.add 'cba'
|
265
|
-
end
|
266
|
-
|
267
|
-
it 'returns false' do
|
268
|
-
expect(node.partial_word? %w(a)).to be false
|
269
|
-
expect(node.partial_word? %w(a b)).to be false
|
270
|
-
expect(node.partial_word? %w(a b c)).to be false
|
271
|
-
end
|
272
|
-
end
|
273
|
-
end
|
274
|
-
end
|
275
|
-
|
276
|
-
describe '#word?' do
|
277
|
-
context 'when the chars array is empty' do
|
278
|
-
context 'when the node is terminal' do
|
279
|
-
before do
|
280
|
-
node.terminal!
|
281
|
-
end
|
282
|
-
|
283
|
-
it 'returns true' do
|
284
|
-
expect(node.word? []).to be true
|
285
|
-
end
|
286
|
-
end
|
287
|
-
|
288
|
-
context 'when the node is not terminal' do
|
289
|
-
it 'returns false' do
|
290
|
-
expect(node.word? []).to be false
|
291
|
-
end
|
292
|
-
end
|
293
|
-
end
|
294
|
-
|
295
|
-
context 'when the chars array is not empty' do
|
296
|
-
context 'when the node has a tree that matches all the characters' do
|
297
|
-
before do
|
298
|
-
node.add 'abc'
|
299
|
-
end
|
300
|
-
|
301
|
-
it 'returns true' do
|
302
|
-
expect(node.word? %w(a b c)).to be true
|
303
|
-
end
|
304
|
-
end
|
305
|
-
|
306
|
-
context 'when the node has a tree that does not match all the characters' do
|
307
|
-
before do
|
308
|
-
node.add 'abc'
|
309
|
-
end
|
310
|
-
|
311
|
-
it 'returns false' do
|
312
|
-
expect(node.word? %w(a)).to be false
|
313
|
-
expect(node.word? %w(a b)).to be false
|
314
|
-
end
|
315
|
-
end
|
316
|
-
end
|
317
|
-
end
|
318
|
-
|
319
|
-
describe '#scan' do
|
320
|
-
context 'when the chars array is empty' do
|
321
|
-
it 'returns itself' do
|
322
|
-
expect(node.scan []).to eq node
|
323
|
-
end
|
324
|
-
end
|
325
|
-
|
326
|
-
context 'when the chars array is not empty' do
|
327
|
-
before do
|
328
|
-
node.add 'cba'
|
329
|
-
end
|
330
|
-
|
331
|
-
context 'when the chars are found' do
|
332
|
-
it 'returns the found child' do
|
333
|
-
expect(node.scan %w(c)).to eq node[:c]
|
334
|
-
expect(node.scan %w(c b)).to eq node[:c][:b]
|
335
|
-
expect(node.scan %w(c b a)).to eq node[:c][:b][:a]
|
336
|
-
end
|
337
|
-
end
|
338
|
-
|
339
|
-
context 'when the chars are not found' do
|
340
|
-
it 'returns a MissingNode' do
|
341
|
-
expect(node.scan %w(a)).to be_a Rambling::Trie::MissingNode
|
342
|
-
expect(node.scan %w(a b)).to be_a Rambling::Trie::MissingNode
|
343
|
-
expect(node.scan %w(a b c)).to be_a Rambling::Trie::MissingNode
|
344
|
-
expect(node.scan %w(c b a d)).to be_a Rambling::Trie::MissingNode
|
345
|
-
end
|
346
|
-
end
|
347
|
-
end
|
348
|
-
end
|
349
|
-
|
350
|
-
describe '#match_prefix' do
|
351
|
-
before do
|
352
|
-
node.letter = :i
|
353
|
-
node.add 'gnite'
|
354
|
-
node.add 'mport'
|
355
|
-
node.add 'mportant'
|
356
|
-
node.add 'mportantly'
|
357
|
-
end
|
358
|
-
|
359
|
-
context 'when the node is terminal' do
|
360
|
-
before do
|
361
|
-
node.terminal!
|
362
|
-
end
|
363
|
-
|
364
|
-
it 'adds itself to the words' do
|
365
|
-
expect(node.match_prefix %w(g n i t e)).to include 'i'
|
366
|
-
end
|
367
|
-
end
|
368
|
-
|
369
|
-
context 'when the node is not terminal' do
|
370
|
-
it 'does not add itself to the words' do
|
371
|
-
expect(node.match_prefix %w(g n i t e)).not_to include 'i'
|
372
|
-
end
|
373
|
-
end
|
374
|
-
|
375
|
-
context 'when the first few chars match a terminal node' do
|
376
|
-
it 'adds those terminal nodes to the words' do
|
377
|
-
words = node.match_prefix(%w(m p o r t a n t l y)).to_a
|
378
|
-
expect(words).to include 'import', 'important', 'importantly'
|
379
|
-
end
|
380
|
-
end
|
381
|
-
|
382
|
-
context 'when the first few chars do not match a terminal node' do
|
383
|
-
it 'does not add any other words found' do
|
384
|
-
words = node.match_prefix(%w(m p m p o r t a n t l y)).to_a
|
385
|
-
expect(words).not_to include 'import', 'important', 'importantly'
|
386
|
-
end
|
387
|
-
end
|
388
|
-
end
|
389
|
-
end
|