line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Tree::SEMETrie; |
2
|
|
|
|
|
|
|
|
3
|
6
|
|
|
6
|
|
165740
|
use 5.006; |
|
6
|
|
|
|
|
25
|
|
|
6
|
|
|
|
|
224
|
|
4
|
6
|
|
|
6
|
|
32
|
use strict; |
|
6
|
|
|
|
|
10
|
|
|
6
|
|
|
|
|
195
|
|
5
|
6
|
|
|
6
|
|
29
|
use warnings; |
|
6
|
|
|
|
|
16
|
|
|
6
|
|
|
|
|
140
|
|
6
|
|
|
|
|
|
|
|
7
|
6
|
|
|
6
|
|
39
|
use List::Util (); |
|
6
|
|
|
|
|
12
|
|
|
6
|
|
|
|
|
117
|
|
8
|
6
|
|
|
6
|
|
11006
|
use Tree::SEMETrie::Iterator (); |
|
6
|
|
|
|
|
16
|
|
|
6
|
|
|
|
|
14581
|
|
9
|
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
=head1 NAME |
11
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
Tree::SEMETrie - Single-Edge Multi-Edge Trie |
13
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
=head1 VERSION |
15
|
|
|
|
|
|
|
|
16
|
|
|
|
|
|
|
Version 0.03 |
17
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
=cut |
19
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
our $VERSION = '0.04'; |
21
|
|
|
|
|
|
|
|
22
|
|
|
|
|
|
|
#Class Constants |
23
|
|
|
|
|
|
|
my $VALUE = 0; |
24
|
|
|
|
|
|
|
my $CHILDS = 1; |
25
|
|
|
|
|
|
|
my $SINGLE_CHILD_KEY = 0; |
26
|
|
|
|
|
|
|
my $SINGLE_CHILD_NODE = 1; |
27
|
|
|
|
|
|
|
|
28
|
|
|
|
|
|
|
#Private Helper Functions |
29
|
|
|
|
|
|
|
|
30
|
|
|
|
|
|
|
#compression algorithm : |
31
|
|
|
|
|
|
|
# if node->value is null and node is only child |
32
|
|
|
|
|
|
|
# node->value = child->value |
33
|
|
|
|
|
|
|
# parent->key += child->key |
34
|
|
|
|
|
|
|
# parent->childs = node->childs |
35
|
|
|
|
|
|
|
my $compress_trie_ref = sub { |
36
|
|
|
|
|
|
|
my ($node, $parent) = @_; |
37
|
|
|
|
|
|
|
|
38
|
|
|
|
|
|
|
#The node must not have a value and have no siblings |
39
|
|
|
|
|
|
|
return if $node->[$VALUE] || ref($parent->[$CHILDS]) ne 'ARRAY'; |
40
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
$parent->[$CHILDS][$SINGLE_CHILD_KEY] .= $node->[$CHILDS][$SINGLE_CHILD_KEY]; |
42
|
|
|
|
|
|
|
$parent->[$CHILDS][$SINGLE_CHILD_NODE] = $node->[$CHILDS][$SINGLE_CHILD_NODE]; |
43
|
|
|
|
|
|
|
|
44
|
|
|
|
|
|
|
return; |
45
|
|
|
|
|
|
|
}; |
46
|
|
|
|
|
|
|
|
47
|
|
|
|
|
|
|
my $default_strategy_ref = sub { $_[0] }; |
48
|
|
|
|
|
|
|
|
49
|
|
|
|
|
|
|
my $find_match_length_ref = sub { |
50
|
|
|
|
|
|
|
my $max_match_length = List::Util::min(length($_[0]), length($_[1])); |
51
|
|
|
|
|
|
|
my $char_iter = 0; |
52
|
|
|
|
|
|
|
for (; $char_iter < $max_match_length; ++$char_iter) { |
53
|
|
|
|
|
|
|
last if substr($_[0], $char_iter, 1) ne substr($_[1], $char_iter, 1); |
54
|
|
|
|
|
|
|
} |
55
|
|
|
|
|
|
|
return $char_iter; |
56
|
|
|
|
|
|
|
}; |
57
|
|
|
|
|
|
|
|
58
|
|
|
|
|
|
|
my $make_new_trie_ref = sub { bless $_[0], ref($_[1]) }; |
59
|
|
|
|
|
|
|
|
60
|
|
|
|
|
|
|
my $split_string_at_position_ref = sub { |
61
|
|
|
|
|
|
|
return ( |
62
|
|
|
|
|
|
|
substr($_[0], 0, $_[1]), |
63
|
|
|
|
|
|
|
substr($_[0], $_[1], 1), |
64
|
|
|
|
|
|
|
substr($_[0], $_[1] + 1), |
65
|
|
|
|
|
|
|
); |
66
|
|
|
|
|
|
|
}; |
67
|
|
|
|
|
|
|
|
68
|
|
|
|
|
|
|
=head1 SYNOPSIS |
69
|
|
|
|
|
|
|
|
70
|
|
|
|
|
|
|
COMING SOON |
71
|
|
|
|
|
|
|
|
72
|
|
|
|
|
|
|
use Tree::SEMETrie; |
73
|
|
|
|
|
|
|
|
74
|
|
|
|
|
|
|
my $trie = Tree::SEMETrie->new(); |
75
|
|
|
|
|
|
|
$trie->add('a long word', 23.7); |
76
|
|
|
|
|
|
|
$trie->add('a longer word', 102); |
77
|
|
|
|
|
|
|
|
78
|
|
|
|
|
|
|
for (my $iterator = $self->iterator; ! $iterator->is_done; $iterator->next) { |
79
|
|
|
|
|
|
|
print $iterator->key . ' => ' . $trie->find($iterator->key)->has_children |
80
|
|
|
|
|
|
|
if $trie->find_value($iterator->key) eq $iterator->value; |
81
|
|
|
|
|
|
|
} |
82
|
|
|
|
|
|
|
|
83
|
|
|
|
|
|
|
$trie->remove($_->[0]) for $trie->all; |
84
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
=head1 SUBROUTINES/METHODS |
86
|
|
|
|
|
|
|
|
87
|
|
|
|
|
|
|
=head2 Constructors |
88
|
|
|
|
|
|
|
|
89
|
|
|
|
|
|
|
=head3 new |
90
|
|
|
|
|
|
|
|
91
|
|
|
|
|
|
|
Create a new empty trie. |
92
|
|
|
|
|
|
|
|
93
|
|
|
|
|
|
|
my $trie = Tree::SEMETrie->new; |
94
|
|
|
|
|
|
|
|
95
|
|
|
|
|
|
|
=cut |
96
|
|
|
|
|
|
|
|
97
|
|
|
|
|
|
|
sub new { |
98
|
21
|
|
|
21
|
1
|
8490
|
my $class = shift; |
99
|
21
|
|
66
|
|
|
93
|
$class = ref $class || $class; |
100
|
21
|
|
|
|
|
69
|
return bless [], $class; |
101
|
|
|
|
|
|
|
} |
102
|
|
|
|
|
|
|
|
103
|
|
|
|
|
|
|
=head2 Root Accessors/Mutators |
104
|
|
|
|
|
|
|
|
105
|
|
|
|
|
|
|
=head3 children |
106
|
|
|
|
|
|
|
|
107
|
|
|
|
|
|
|
Get the list of all immediate [edge => subtrie] pairs. |
108
|
|
|
|
|
|
|
|
109
|
|
|
|
|
|
|
my @edge_subtrie_pairs = $trie->children; |
110
|
|
|
|
|
|
|
my ($edge, $subtrie) = @{$edge_subtrie_pairs[0]}; |
111
|
|
|
|
|
|
|
|
112
|
|
|
|
|
|
|
=head3 childs |
113
|
|
|
|
|
|
|
|
114
|
|
|
|
|
|
|
Alias for children. |
115
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
=cut |
117
|
|
|
|
|
|
|
|
118
|
|
|
|
|
|
|
sub childs { |
119
|
1
|
|
|
1
|
1
|
2
|
my $self = shift; |
120
|
1
|
|
|
|
|
3
|
my $childs_ref = $self->[$CHILDS]; |
121
|
1
|
|
|
|
|
2
|
my $childs_type = ref($childs_ref); |
122
|
|
|
|
|
|
|
return |
123
|
0
|
|
|
|
|
0
|
$childs_type eq 'ARRAY' ? [$childs_ref->[$SINGLE_CHILD_KEY] => $make_new_trie_ref->($childs_ref->[$SINGLE_CHILD_NODE], $self)] : |
124
|
1
|
50
|
|
|
|
8
|
$childs_type eq 'HASH' ? map { [$_ => $make_new_trie_ref->($childs_ref->{$_}, $self)] } keys %$childs_ref : |
|
|
50
|
|
|
|
|
|
125
|
|
|
|
|
|
|
(); |
126
|
|
|
|
|
|
|
} |
127
|
|
|
|
|
|
|
*children = \&childs; |
128
|
|
|
|
|
|
|
|
129
|
|
|
|
|
|
|
=head3 value |
130
|
|
|
|
|
|
|
|
131
|
|
|
|
|
|
|
Get/Set the value of the root. Return undef if there is no value. |
132
|
|
|
|
|
|
|
|
133
|
|
|
|
|
|
|
my $new_value = $trie->value($new_value); |
134
|
|
|
|
|
|
|
|
135
|
|
|
|
|
|
|
=cut |
136
|
|
|
|
|
|
|
|
137
|
|
|
|
|
|
|
sub value { |
138
|
14
|
|
|
14
|
1
|
19
|
my $self = shift; |
139
|
14
|
100
|
|
|
|
36
|
if (@_) { ${$self->[$VALUE]} = $_[0] } |
|
4
|
|
|
|
|
5
|
|
|
4
|
|
|
|
|
9
|
|
140
|
14
|
100
|
|
|
|
37
|
return $self->[$VALUE] ? ${$self->[$VALUE]} : undef; |
|
13
|
|
|
|
|
51
|
|
141
|
|
|
|
|
|
|
} |
142
|
|
|
|
|
|
|
|
143
|
|
|
|
|
|
|
=head2 Root Verifiers |
144
|
|
|
|
|
|
|
|
145
|
|
|
|
|
|
|
=head3 has_children |
146
|
|
|
|
|
|
|
|
147
|
|
|
|
|
|
|
Return true if the root has any child paths. |
148
|
|
|
|
|
|
|
|
149
|
|
|
|
|
|
|
$trie->has_children; |
150
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
=head3 has_childs |
152
|
|
|
|
|
|
|
|
153
|
|
|
|
|
|
|
Alias for has_children. |
154
|
|
|
|
|
|
|
|
155
|
|
|
|
|
|
|
=cut |
156
|
|
|
|
|
|
|
|
157
|
1
|
|
|
1
|
1
|
15
|
sub has_childs { ref($_[0][$CHILDS]) ne '' } |
158
|
|
|
|
|
|
|
|
159
|
|
|
|
|
|
|
*has_children = \&has_childs; |
160
|
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
=head3 has_value |
162
|
|
|
|
|
|
|
|
163
|
|
|
|
|
|
|
Return true if the root has an associated value. |
164
|
|
|
|
|
|
|
|
165
|
|
|
|
|
|
|
$trie->has_value; |
166
|
|
|
|
|
|
|
|
167
|
|
|
|
|
|
|
=cut |
168
|
|
|
|
|
|
|
|
169
|
24
|
|
|
24
|
1
|
93
|
sub has_value { defined $_[0][$VALUE] } |
170
|
|
|
|
|
|
|
|
171
|
|
|
|
|
|
|
=head2 Trie Accessors |
172
|
|
|
|
|
|
|
|
173
|
|
|
|
|
|
|
=head3 find |
174
|
|
|
|
|
|
|
|
175
|
|
|
|
|
|
|
Find the root of a subtrie that matches the given key. If no such subtrie exists, return undef. |
176
|
|
|
|
|
|
|
|
177
|
|
|
|
|
|
|
my $subtrie = $trie->find($key); |
178
|
|
|
|
|
|
|
|
179
|
|
|
|
|
|
|
=head3 lookup |
180
|
|
|
|
|
|
|
|
181
|
|
|
|
|
|
|
Alias for find. |
182
|
|
|
|
|
|
|
|
183
|
|
|
|
|
|
|
=cut |
184
|
|
|
|
|
|
|
|
185
|
|
|
|
|
|
|
sub find { |
186
|
41
|
|
|
41
|
1
|
200
|
my $self = shift; |
187
|
41
|
|
|
|
|
57
|
my ($key) = @_; |
188
|
|
|
|
|
|
|
|
189
|
41
|
100
|
|
|
|
99
|
return undef unless defined $key; |
190
|
|
|
|
|
|
|
|
191
|
40
|
|
|
|
|
40
|
my $node = $self; |
192
|
|
|
|
|
|
|
|
193
|
40
|
|
|
|
|
69
|
my ($key_iter, $key_length) = (0, length $key); |
194
|
40
|
|
|
|
|
81
|
while ($key_iter < $key_length) { |
195
|
68
|
|
|
|
|
109
|
my $childs_type = ref($node->[$CHILDS]); |
196
|
|
|
|
|
|
|
|
197
|
|
|
|
|
|
|
#Key does not exist since we're at the end of the trie |
198
|
68
|
100
|
|
|
|
148
|
if (! $childs_type) { $node = undef; last } |
|
6
|
100
|
|
|
|
9
|
|
|
6
|
|
|
|
|
8
|
|
199
|
|
|
|
|
|
|
|
200
|
|
|
|
|
|
|
#Check within the compressed trie node |
201
|
|
|
|
|
|
|
elsif ($childs_type eq 'ARRAY') { |
202
|
|
|
|
|
|
|
#Determine where the keys match |
203
|
27
|
|
|
|
|
37
|
my $old_key = $node->[$CHILDS][$SINGLE_CHILD_KEY]; |
204
|
27
|
|
|
|
|
36
|
my $old_key_length = length $old_key; |
205
|
27
|
|
|
|
|
71
|
my $match_length = $find_match_length_ref->(substr($key, $key_iter), $old_key); |
206
|
|
|
|
|
|
|
|
207
|
|
|
|
|
|
|
#The new key contains all of the old key |
208
|
27
|
100
|
|
|
|
66
|
if($match_length == $old_key_length) { |
|
|
50
|
|
|
|
|
|
209
|
|
|
|
|
|
|
#Move to the end of the compressed node |
210
|
26
|
|
|
|
|
38
|
$node = $node->[$CHILDS][$SINGLE_CHILD_NODE]; |
211
|
|
|
|
|
|
|
#Move to the next part of the key |
212
|
26
|
|
|
|
|
67
|
$key_iter += $match_length; |
213
|
|
|
|
|
|
|
|
214
|
|
|
|
|
|
|
#The old key contains all of the new key |
215
|
|
|
|
|
|
|
} elsif($match_length == $key_length - $key_iter) { |
216
|
|
|
|
|
|
|
#Create a new trie containing the unmatched suffix of the matched key and its sub-trie |
217
|
1
|
|
|
|
|
2
|
my $new_node = []; |
218
|
1
|
|
|
|
|
2
|
$new_node->[$CHILDS][$SINGLE_CHILD_KEY] = substr($old_key, $match_length); |
219
|
1
|
|
|
|
|
3
|
$new_node->[$CHILDS][$SINGLE_CHILD_NODE] = $node->[$CHILDS][$SINGLE_CHILD_NODE]; |
220
|
1
|
|
|
|
|
1
|
$node = $new_node; |
221
|
1
|
|
|
|
|
2
|
last; |
222
|
|
|
|
|
|
|
|
223
|
|
|
|
|
|
|
#There was a mismatch in the comparison so the key doesn't exist |
224
|
0
|
|
|
|
|
0
|
} else { $node = undef; last } |
|
0
|
|
|
|
|
0
|
|
225
|
|
|
|
|
|
|
|
226
|
|
|
|
|
|
|
#Keep expanding down the trie |
227
|
|
|
|
|
|
|
} else { |
228
|
35
|
|
|
|
|
60
|
$node = $node->[$CHILDS]{substr($key, $key_iter, 1)}; |
229
|
35
|
|
|
|
|
67
|
++$key_iter; |
230
|
|
|
|
|
|
|
} |
231
|
|
|
|
|
|
|
} |
232
|
|
|
|
|
|
|
|
233
|
40
|
100
|
|
|
|
124
|
return $node ? $make_new_trie_ref->($node, $self) : undef; |
234
|
|
|
|
|
|
|
} |
235
|
|
|
|
|
|
|
*lookup = \&find; |
236
|
|
|
|
|
|
|
|
237
|
|
|
|
|
|
|
=head3 find_value |
238
|
|
|
|
|
|
|
|
239
|
|
|
|
|
|
|
Find the value associated with the given key. If no such key exists, return undef. |
240
|
|
|
|
|
|
|
|
241
|
|
|
|
|
|
|
my $value = $trie->find_value($key); |
242
|
|
|
|
|
|
|
|
243
|
|
|
|
|
|
|
=head3 lookup_value |
244
|
|
|
|
|
|
|
|
245
|
|
|
|
|
|
|
Alias for find_value. |
246
|
|
|
|
|
|
|
|
247
|
|
|
|
|
|
|
=cut |
248
|
|
|
|
|
|
|
|
249
|
|
|
|
|
|
|
sub find_value { |
250
|
3
|
|
|
3
|
1
|
10
|
my $self = shift; |
251
|
|
|
|
|
|
|
|
252
|
3
|
|
|
|
|
543
|
my $entry = $self->find(@_); |
253
|
3
|
50
|
|
|
|
10
|
return $entry ? $entry->value : undef; |
254
|
|
|
|
|
|
|
} |
255
|
|
|
|
|
|
|
*lookup_value = \&find_value; |
256
|
|
|
|
|
|
|
|
257
|
|
|
|
|
|
|
=head2 Trie Mutators |
258
|
|
|
|
|
|
|
|
259
|
|
|
|
|
|
|
=head3 add |
260
|
|
|
|
|
|
|
|
261
|
|
|
|
|
|
|
Insert a key into the trie. Return a reference to the key's value. In the case |
262
|
|
|
|
|
|
|
of a pre-existing key, the strategy function determines which value is stored. |
263
|
|
|
|
|
|
|
The default strategy function chooses the original value. |
264
|
|
|
|
|
|
|
|
265
|
|
|
|
|
|
|
$trie->add('some path'); |
266
|
|
|
|
|
|
|
$trie->add('some path', 'optional value'); |
267
|
|
|
|
|
|
|
$trie->add('some path', 'new value to be ignored', sub { $_[0] }); |
268
|
|
|
|
|
|
|
$trie->add('some path', 'new value to be inserted', sub { $_[1] }); |
269
|
|
|
|
|
|
|
|
270
|
|
|
|
|
|
|
A custom strategy must conform to the following interface: |
271
|
|
|
|
|
|
|
|
272
|
|
|
|
|
|
|
sub new_strategy { |
273
|
|
|
|
|
|
|
my ($current_value, $new_value) = @_; |
274
|
|
|
|
|
|
|
return $desired_value; |
275
|
|
|
|
|
|
|
} |
276
|
|
|
|
|
|
|
|
277
|
|
|
|
|
|
|
=head3 insert |
278
|
|
|
|
|
|
|
|
279
|
|
|
|
|
|
|
Alias for add. |
280
|
|
|
|
|
|
|
|
281
|
|
|
|
|
|
|
=cut |
282
|
|
|
|
|
|
|
|
283
|
|
|
|
|
|
|
sub add { |
284
|
81
|
|
|
81
|
1
|
209
|
my $self = shift; |
285
|
81
|
|
|
|
|
107
|
my ($key, $value, $strategy_ref) = @_; |
286
|
|
|
|
|
|
|
|
287
|
|
|
|
|
|
|
#No path should ever exist for undef |
288
|
81
|
100
|
|
|
|
155
|
return undef unless defined $key; |
289
|
|
|
|
|
|
|
|
290
|
80
|
|
66
|
|
|
238
|
$strategy_ref ||= $default_strategy_ref; |
291
|
|
|
|
|
|
|
|
292
|
80
|
|
|
|
|
75
|
my $node = $self; |
293
|
|
|
|
|
|
|
|
294
|
80
|
|
|
|
|
110
|
my ($key_iter, $key_length) = (0, length $key); |
295
|
80
|
|
|
|
|
137
|
while ($key_iter < $key_length) { |
296
|
178
|
|
|
|
|
242
|
my $childs_type = ref($node->[$CHILDS]); |
297
|
|
|
|
|
|
|
|
298
|
|
|
|
|
|
|
#There are no branches so we've found a new key |
299
|
178
|
100
|
|
|
|
307
|
if (! $childs_type) { |
|
|
100
|
|
|
|
|
|
300
|
|
|
|
|
|
|
#Create a new branch for the suffix and move down the trie |
301
|
50
|
|
|
|
|
78
|
my $single_child = $node->[$CHILDS] = []; |
302
|
|
|
|
|
|
|
|
303
|
50
|
|
|
|
|
96
|
$single_child->[$SINGLE_CHILD_KEY] = substr($key, $key_iter); |
304
|
50
|
|
|
|
|
70
|
$node = $single_child->[$SINGLE_CHILD_NODE] = []; |
305
|
50
|
|
|
|
|
67
|
last; |
306
|
|
|
|
|
|
|
|
307
|
|
|
|
|
|
|
#There is exactly 1 current branch |
308
|
|
|
|
|
|
|
} elsif ($childs_type eq 'ARRAY') { |
309
|
|
|
|
|
|
|
|
310
|
|
|
|
|
|
|
#Determine where the keys match |
311
|
50
|
|
|
|
|
60
|
my $old_key = $node->[$CHILDS][$SINGLE_CHILD_KEY]; |
312
|
50
|
|
|
|
|
53
|
my $old_key_length = length $old_key; |
313
|
50
|
|
|
|
|
116
|
my $match_length = $find_match_length_ref->(substr($key, $key_iter), $old_key); |
314
|
|
|
|
|
|
|
|
315
|
|
|
|
|
|
|
#The new key contains all of the old key |
316
|
50
|
100
|
|
|
|
126
|
if($match_length == $old_key_length) { |
|
|
100
|
|
|
|
|
|
317
|
5
|
|
|
|
|
8
|
$node = $node->[$CHILDS][$SINGLE_CHILD_NODE]; |
318
|
5
|
|
|
|
|
10
|
$key_iter += $match_length; |
319
|
|
|
|
|
|
|
|
320
|
|
|
|
|
|
|
#The old key contains all of the new key |
321
|
|
|
|
|
|
|
} elsif($match_length == $key_length - $key_iter) { |
322
|
|
|
|
|
|
|
|
323
|
|
|
|
|
|
|
#Fetch and save the current child branch so that we can split it |
324
|
8
|
|
|
|
|
9
|
my $old_single_child = $node->[$CHILDS]; |
325
|
|
|
|
|
|
|
#The unmatched suffix still points to the same trie |
326
|
8
|
|
|
|
|
13
|
$old_single_child->[$SINGLE_CHILD_KEY] = substr($old_key, $match_length); |
327
|
|
|
|
|
|
|
|
328
|
|
|
|
|
|
|
#Create a new branch point |
329
|
8
|
|
|
|
|
9
|
my $new_single_child = $node->[$CHILDS] = []; |
330
|
|
|
|
|
|
|
#Insert the matched prefix |
331
|
8
|
|
|
|
|
13
|
$new_single_child->[$SINGLE_CHILD_KEY] = substr($key, $key_iter); |
332
|
|
|
|
|
|
|
#Move down the trie to the newly inserted branch point |
333
|
8
|
|
|
|
|
12
|
$node = $new_single_child->[$SINGLE_CHILD_NODE] = []; |
334
|
|
|
|
|
|
|
#Make the unmatched suffix a subtrie of the matched prefix |
335
|
8
|
|
|
|
|
10
|
$node->[$CHILDS] = $old_single_child; |
336
|
8
|
|
|
|
|
8
|
last; |
337
|
|
|
|
|
|
|
|
338
|
|
|
|
|
|
|
} else { |
339
|
|
|
|
|
|
|
|
340
|
37
|
|
|
|
|
62
|
my ($key_match, $old_key_diff, $old_key_tail) = $split_string_at_position_ref->($old_key, $match_length); |
341
|
37
|
|
|
|
|
54
|
my $new_key_diff = substr($key, $key_iter + $match_length, 1); |
342
|
|
|
|
|
|
|
|
343
|
|
|
|
|
|
|
#Fetch and save the current child branch so that we can split it later |
344
|
37
|
|
|
|
|
42
|
my $old_single_child = $node->[$CHILDS]; |
345
|
|
|
|
|
|
|
|
346
|
|
|
|
|
|
|
#The match may occur in the middle |
347
|
37
|
100
|
|
|
|
70
|
if ($key_match ne '') { |
348
|
|
|
|
|
|
|
#Create a new branch to represent the match |
349
|
10
|
|
|
|
|
13
|
my $match_childs_ref = $node->[$CHILDS] = []; |
350
|
10
|
|
|
|
|
16
|
$match_childs_ref->[$SINGLE_CHILD_KEY] = $key_match; |
351
|
|
|
|
|
|
|
#Move down the branch to the end fo the match |
352
|
10
|
|
|
|
|
20
|
$node = $match_childs_ref->[$SINGLE_CHILD_NODE] = []; |
353
|
|
|
|
|
|
|
} |
354
|
|
|
|
|
|
|
|
355
|
|
|
|
|
|
|
#Create a new branch to represent the divergence |
356
|
37
|
|
|
|
|
51
|
my $branch_childs_ref = $node->[$CHILDS] = {}; |
357
|
|
|
|
|
|
|
|
358
|
|
|
|
|
|
|
#The match may occur at the end of the old key, so the old key's child becomes the divergence's child |
359
|
37
|
100
|
|
|
|
83
|
if ($old_key_tail eq '') { |
360
|
19
|
|
|
|
|
33
|
$branch_childs_ref->{$old_key_diff} = $old_single_child->[$SINGLE_CHILD_NODE]; |
361
|
|
|
|
|
|
|
|
362
|
|
|
|
|
|
|
#Otherwise make the old branch a child of the old branch's divergence point |
363
|
|
|
|
|
|
|
} else { |
364
|
|
|
|
|
|
|
#Replace the old key with the suffix after the difference |
365
|
18
|
|
|
|
|
20
|
$old_single_child->[$SINGLE_CHILD_KEY] = $old_key_tail; |
366
|
18
|
|
|
|
|
38
|
$branch_childs_ref->{$old_key_diff}[$CHILDS] = $old_single_child; |
367
|
|
|
|
|
|
|
} |
368
|
|
|
|
|
|
|
|
369
|
|
|
|
|
|
|
#Make the new branch a child of the new branch's divergence point |
370
|
37
|
|
|
|
|
66
|
$node = $branch_childs_ref->{$new_key_diff} = []; |
371
|
|
|
|
|
|
|
|
372
|
|
|
|
|
|
|
#Move past the branch point |
373
|
37
|
|
|
|
|
112
|
$key_iter += $match_length + 1; |
374
|
|
|
|
|
|
|
} |
375
|
|
|
|
|
|
|
|
376
|
|
|
|
|
|
|
#Otherwise this node has multiple branches |
377
|
|
|
|
|
|
|
} else { |
378
|
|
|
|
|
|
|
#Retrieve the next node in the trie, creating a new one when necessary |
379
|
78
|
|
50
|
|
|
168
|
$node = $node->[$CHILDS]{substr($key, $key_iter, 1)} ||= []; |
380
|
78
|
|
|
|
|
133
|
++$key_iter; |
381
|
|
|
|
|
|
|
} |
382
|
|
|
|
|
|
|
} |
383
|
|
|
|
|
|
|
|
384
|
|
|
|
|
|
|
#Assign the value based on the strategy |
385
|
80
|
|
|
|
|
118
|
${$node->[$VALUE]} = $node->[$VALUE] |
|
2
|
|
|
|
|
8
|
|
386
|
80
|
100
|
|
|
|
153
|
? $strategy_ref->(${$node->[$VALUE]}, $value) |
387
|
|
|
|
|
|
|
: $value; |
388
|
|
|
|
|
|
|
|
389
|
80
|
|
|
|
|
203
|
return $node->[$VALUE]; |
390
|
|
|
|
|
|
|
} |
391
|
|
|
|
|
|
|
*insert = \&add; |
392
|
|
|
|
|
|
|
|
393
|
|
|
|
|
|
|
=head3 erase |
394
|
|
|
|
|
|
|
|
395
|
|
|
|
|
|
|
Remove a key from the trie. Return the value associated with the removed key. |
396
|
|
|
|
|
|
|
|
397
|
|
|
|
|
|
|
my $optional_value = $trie->erase('some path'); |
398
|
|
|
|
|
|
|
|
399
|
|
|
|
|
|
|
=head3 remove |
400
|
|
|
|
|
|
|
|
401
|
|
|
|
|
|
|
Alias for erase. |
402
|
|
|
|
|
|
|
|
403
|
|
|
|
|
|
|
=cut |
404
|
|
|
|
|
|
|
|
405
|
|
|
|
|
|
|
sub erase { |
406
|
14
|
|
|
14
|
1
|
43
|
my $self = shift; |
407
|
14
|
|
|
|
|
19
|
my ($key) = @_; |
408
|
|
|
|
|
|
|
|
409
|
|
|
|
|
|
|
#No path should ever exist for undef |
410
|
14
|
100
|
|
|
|
39
|
return undef unless defined $key; |
411
|
|
|
|
|
|
|
|
412
|
13
|
|
|
|
|
14
|
my $grand_parent_node = undef; |
413
|
13
|
|
|
|
|
13
|
my $parent_node = undef; |
414
|
13
|
|
|
|
|
13
|
my $node = $self; |
415
|
|
|
|
|
|
|
|
416
|
13
|
|
|
|
|
21
|
my ($key_iter, $key_length) = (0, length $key); |
417
|
13
|
|
|
|
|
618
|
while ($key_iter < $key_length) { |
418
|
26
|
|
|
|
|
50
|
my $childs_type = ref($node->[$CHILDS]); |
419
|
|
|
|
|
|
|
|
420
|
|
|
|
|
|
|
#Key does not exist since we're at the end of the trie |
421
|
26
|
100
|
|
|
|
60
|
if (! $childs_type) { $node = undef; last } |
|
1
|
100
|
|
|
|
2
|
|
|
1
|
|
|
|
|
1
|
|
422
|
|
|
|
|
|
|
|
423
|
|
|
|
|
|
|
#Check within the compressed trie node |
424
|
|
|
|
|
|
|
elsif ($childs_type eq 'ARRAY') { |
425
|
|
|
|
|
|
|
|
426
|
|
|
|
|
|
|
#Determine where the keys match |
427
|
10
|
|
|
|
|
15
|
my $old_key = $node->[$CHILDS][$SINGLE_CHILD_KEY]; |
428
|
10
|
|
|
|
|
11
|
my $old_key_length = length $old_key; |
429
|
10
|
|
|
|
|
26
|
my $match_length = $find_match_length_ref->(substr($key, $key_iter), $old_key); |
430
|
|
|
|
|
|
|
|
431
|
|
|
|
|
|
|
#The deleted key contains all of the old key |
432
|
10
|
100
|
|
|
|
25
|
if($match_length == $old_key_length) { |
433
|
|
|
|
|
|
|
|
434
|
|
|
|
|
|
|
#Save the parent |
435
|
9
|
|
|
|
|
13
|
$grand_parent_node = $parent_node; |
436
|
9
|
|
|
|
|
10
|
$parent_node = $node; |
437
|
|
|
|
|
|
|
#Move to the end of the compressed node |
438
|
9
|
|
|
|
|
14
|
$node = $node->[$CHILDS][$SINGLE_CHILD_NODE]; |
439
|
|
|
|
|
|
|
#Move to the next part of the key |
440
|
9
|
|
|
|
|
24
|
$key_iter += $match_length; |
441
|
|
|
|
|
|
|
|
442
|
|
|
|
|
|
|
#There was a mismatch in the comparison so the deleted key doesn't exist |
443
|
1
|
|
|
|
|
1
|
} else { $node = undef; last } |
|
1
|
|
|
|
|
2
|
|
444
|
|
|
|
|
|
|
|
445
|
|
|
|
|
|
|
#Keep expanding down the trie |
446
|
|
|
|
|
|
|
} else { |
447
|
|
|
|
|
|
|
|
448
|
|
|
|
|
|
|
#Save the parent |
449
|
15
|
|
|
|
|
49
|
$grand_parent_node = $parent_node; |
450
|
15
|
|
|
|
|
13
|
$parent_node = $node; |
451
|
|
|
|
|
|
|
#Move to the next node |
452
|
15
|
|
|
|
|
20
|
$node = $node->[$CHILDS]{substr($key, $key_iter, 1)}; |
453
|
15
|
|
|
|
|
27
|
++$key_iter; |
454
|
|
|
|
|
|
|
} |
455
|
|
|
|
|
|
|
} |
456
|
|
|
|
|
|
|
|
457
|
13
|
|
|
|
|
12
|
my $deleted_value; |
458
|
13
|
100
|
100
|
|
|
57
|
if ($node && $node->[$VALUE]) { |
459
|
10
|
|
|
|
|
13
|
$deleted_value = ${delete $node->[$VALUE]}; |
|
10
|
|
|
|
|
17
|
|
460
|
|
|
|
|
|
|
|
461
|
10
|
|
|
|
|
16
|
my $childs_type = ref($node->[$CHILDS]); |
462
|
|
|
|
|
|
|
|
463
|
|
|
|
|
|
|
#The node has no children |
464
|
10
|
100
|
|
|
|
25
|
if (! $childs_type) { |
|
|
100
|
|
|
|
|
|
465
|
8
|
|
|
|
|
12
|
my $parent_childs_ref = $parent_node->[$CHILDS]; |
466
|
8
|
|
|
|
|
11
|
my $parent_childs_type = ref($parent_childs_ref); |
467
|
|
|
|
|
|
|
|
468
|
|
|
|
|
|
|
#The node may have siblings |
469
|
8
|
100
|
|
|
|
20
|
if ($parent_childs_type eq 'HASH') { |
470
|
|
|
|
|
|
|
#Final character of the key must be the branch point |
471
|
1
|
|
|
|
|
3
|
delete $parent_childs_ref->{substr($key, -1)}; |
472
|
|
|
|
|
|
|
|
473
|
|
|
|
|
|
|
#The sibling may now be an only child |
474
|
1
|
50
|
|
|
|
4
|
if (keys(%$parent_childs_ref) == 1) { |
475
|
|
|
|
|
|
|
#Fix the representation |
476
|
1
|
|
|
|
|
2
|
$parent_node->[$CHILDS] = []; |
477
|
1
|
|
|
|
|
2
|
@{$parent_node->[$CHILDS]}[$SINGLE_CHILD_KEY, $SINGLE_CHILD_NODE] = each %$parent_childs_ref; |
|
1
|
|
|
|
|
4
|
|
478
|
|
|
|
|
|
|
|
479
|
|
|
|
|
|
|
#Try to repair the divergence, which splits a key into 3 |
480
|
1
|
|
|
|
|
3
|
$compress_trie_ref->($parent_node->[$CHILDS][$SINGLE_CHILD_NODE], $parent_node); |
481
|
1
|
|
|
|
|
1
|
$compress_trie_ref->($parent_node, $grand_parent_node); |
482
|
|
|
|
|
|
|
} |
483
|
|
|
|
|
|
|
|
484
|
|
|
|
|
|
|
#The node has no siblings |
485
|
|
|
|
|
|
|
} else { |
486
|
7
|
|
|
|
|
15
|
delete $parent_node->[$CHILDS]; |
487
|
|
|
|
|
|
|
} |
488
|
|
|
|
|
|
|
|
489
|
|
|
|
|
|
|
#The node has 1 child |
490
|
|
|
|
|
|
|
} elsif ($childs_type eq 'ARRAY') { |
491
|
1
|
|
|
|
|
3
|
$compress_trie_ref->($node, $parent_node); |
492
|
|
|
|
|
|
|
} |
493
|
|
|
|
|
|
|
} |
494
|
|
|
|
|
|
|
|
495
|
13
|
|
|
|
|
76
|
return $deleted_value; |
496
|
|
|
|
|
|
|
} |
497
|
|
|
|
|
|
|
*remove = \&erase; |
498
|
|
|
|
|
|
|
|
499
|
|
|
|
|
|
|
=head3 merge |
500
|
|
|
|
|
|
|
|
501
|
|
|
|
|
|
|
IN DEVELOPMENT |
502
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
=cut |
504
|
|
|
|
|
|
|
|
505
|
|
|
|
|
|
|
sub merge { |
506
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
507
|
0
|
|
|
|
|
|
my ($key, $trie, $strategy_ref) = @_; |
508
|
|
|
|
|
|
|
|
509
|
|
|
|
|
|
|
#No path should ever exist for undef |
510
|
0
|
0
|
|
|
|
|
return undef unless defined $key; |
511
|
|
|
|
|
|
|
|
512
|
0
|
|
0
|
|
|
|
$strategy_ref ||= $default_strategy_ref; |
513
|
|
|
|
|
|
|
|
514
|
0
|
|
|
|
|
|
my $preexisting_value = $self->add($key); |
515
|
0
|
|
|
|
|
|
my $merge_point = $self->find($key); |
516
|
|
|
|
|
|
|
|
517
|
0
|
|
|
|
|
|
my $childs_type = ref($merge_point->[$CHILDS]); |
518
|
0
|
0
|
|
|
|
|
if (! $childs_type) { |
519
|
0
|
|
|
|
|
|
$merge_point->[$CHILDS] = $trie->[$CHILDS]; |
520
|
|
|
|
|
|
|
|
521
|
0
|
0
|
|
|
|
|
$merge_point->[$VALUE] = $preexisting_value |
522
|
|
|
|
|
|
|
? $trie->[$VALUE] |
523
|
|
|
|
|
|
|
: $strategy_ref->($merge_point->[$VALUE], $trie->[$VALUE]); |
524
|
0
|
0
|
|
|
|
|
$compress_trie_ref->($merge_point->[$CHILDS][$SINGLE_CHILD_NODE], $merge_point) |
525
|
|
|
|
|
|
|
if ref($merge_point->[$CHILDS]) eq 'ARRAY'; |
526
|
|
|
|
|
|
|
|
527
|
|
|
|
|
|
|
#We need to consider how to merge |
528
|
|
|
|
|
|
|
} else { |
529
|
|
|
|
|
|
|
#both single |
530
|
|
|
|
|
|
|
# |
531
|
|
|
|
|
|
|
#both multi |
532
|
|
|
|
|
|
|
# |
533
|
|
|
|
|
|
|
# |
534
|
|
|
|
|
|
|
|
535
|
|
|
|
|
|
|
#m-om-my - asdga |
536
|
|
|
|
|
|
|
# ma - sdaa |
537
|
|
|
|
|
|
|
#= |
538
|
|
|
|
|
|
|
#m-om-m-y-asdga |
539
|
|
|
|
|
|
|
# a-sdaa |
540
|
|
|
|
|
|
|
# |
541
|
|
|
|
|
|
|
#m-om-may |
542
|
|
|
|
|
|
|
#m-om m-a |
543
|
|
|
|
|
|
|
# d-ad |
544
|
|
|
|
|
|
|
#= |
545
|
|
|
|
|
|
|
#m-om-m-a-y |
546
|
|
|
|
|
|
|
# d-ad |
547
|
|
|
|
|
|
|
# |
548
|
|
|
|
|
|
|
#m-om-m-y |
549
|
|
|
|
|
|
|
# m-as |
550
|
|
|
|
|
|
|
#m-om m-a |
551
|
|
|
|
|
|
|
# d-ad |
552
|
|
|
|
|
|
|
#= |
553
|
|
|
|
|
|
|
#m-om-m-y |
554
|
|
|
|
|
|
|
# a-s |
555
|
|
|
|
|
|
|
# d-ad |
556
|
|
|
|
|
|
|
|
557
|
|
|
|
|
|
|
} |
558
|
|
|
|
|
|
|
|
559
|
|
|
|
|
|
|
} |
560
|
|
|
|
|
|
|
|
561
|
|
|
|
|
|
|
=head3 prune |
562
|
|
|
|
|
|
|
|
563
|
|
|
|
|
|
|
IN DEVELOPMENT |
564
|
|
|
|
|
|
|
|
565
|
|
|
|
|
|
|
Remove the entire subtrie of the given key. Return the removed subtrie. |
566
|
|
|
|
|
|
|
|
567
|
|
|
|
|
|
|
=cut |
568
|
|
|
|
|
|
|
|
569
|
|
|
|
|
|
|
sub prune { |
570
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
571
|
0
|
|
|
|
|
|
my ($key) = @_; |
572
|
|
|
|
|
|
|
|
573
|
|
|
|
|
|
|
#No path should ever exist for undef |
574
|
0
|
0
|
|
|
|
|
return undef unless defined $key; |
575
|
|
|
|
|
|
|
|
576
|
0
|
|
|
|
|
|
my $grand_parent_node = undef; |
577
|
0
|
|
|
|
|
|
my $parent_node = undef; |
578
|
0
|
|
|
|
|
|
my $node = $self; |
579
|
|
|
|
|
|
|
|
580
|
0
|
|
|
|
|
|
my ($key_iter, $key_length) = (0, length $key); |
581
|
0
|
|
|
|
|
|
while ($key_iter < $key_length) { |
582
|
0
|
|
|
|
|
|
my $childs_type = ref($node->[$CHILDS]); |
583
|
|
|
|
|
|
|
|
584
|
|
|
|
|
|
|
#Key does not exist since we're at the end of the trie |
585
|
0
|
0
|
|
|
|
|
if (! $childs_type) { $node = undef; last } |
|
0
|
0
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
586
|
|
|
|
|
|
|
|
587
|
|
|
|
|
|
|
#Check within the compressed trie node |
588
|
|
|
|
|
|
|
elsif ($childs_type eq 'ARRAY') { |
589
|
|
|
|
|
|
|
|
590
|
|
|
|
|
|
|
#Determine where the keys match |
591
|
0
|
|
|
|
|
|
my $old_key = $node->[$CHILDS][$SINGLE_CHILD_KEY]; |
592
|
0
|
|
|
|
|
|
my $old_key_length = length $old_key; |
593
|
0
|
|
|
|
|
|
my $match_length = $find_match_length_ref->(substr($key, $key_iter), $old_key); |
594
|
|
|
|
|
|
|
|
595
|
|
|
|
|
|
|
#The pruning key contains all of the old key |
596
|
0
|
0
|
|
|
|
|
if($match_length == $old_key_length) { |
|
|
0
|
|
|
|
|
|
597
|
|
|
|
|
|
|
|
598
|
|
|
|
|
|
|
#Save the parent |
599
|
0
|
|
|
|
|
|
$grand_parent_node = $parent_node; |
600
|
0
|
|
|
|
|
|
$parent_node = $node; |
601
|
|
|
|
|
|
|
#Move to the end of the compressed node |
602
|
0
|
|
|
|
|
|
$node = $node->[$CHILDS][$SINGLE_CHILD_NODE]; |
603
|
|
|
|
|
|
|
#Move to the next part of the key |
604
|
0
|
|
|
|
|
|
$key_iter += $match_length; |
605
|
|
|
|
|
|
|
|
606
|
|
|
|
|
|
|
#The old key contains all of the pruning key |
607
|
|
|
|
|
|
|
} elsif($match_length == $key_length - $key_iter) { |
608
|
|
|
|
|
|
|
|
609
|
|
|
|
|
|
|
#Create a new trie containing the unmatched suffix of the matched key and its sub-trie |
610
|
0
|
|
|
|
|
|
my $new_node = [undef, [substr($old_key, $match_length) => $node->[$CHILDS][$SINGLE_CHILD_NODE]]]; |
611
|
|
|
|
|
|
|
|
612
|
|
|
|
|
|
|
#Save the parent |
613
|
0
|
|
|
|
|
|
$grand_parent_node = $parent_node; |
614
|
0
|
|
|
|
|
|
$parent_node = $node; |
615
|
|
|
|
|
|
|
#Kill the dangling edge |
616
|
0
|
|
|
|
|
|
delete $node->[$CHILDS]; |
617
|
0
|
|
|
|
|
|
$node = $new_node; |
618
|
|
|
|
|
|
|
|
619
|
0
|
|
|
|
|
|
last; |
620
|
0
|
|
|
|
|
|
} else { $node = undef; last } |
|
0
|
|
|
|
|
|
|
621
|
|
|
|
|
|
|
|
622
|
|
|
|
|
|
|
#Keep expanding down the trie |
623
|
|
|
|
|
|
|
} else { |
624
|
|
|
|
|
|
|
|
625
|
|
|
|
|
|
|
#Save the parent |
626
|
0
|
|
|
|
|
|
$grand_parent_node = $parent_node; |
627
|
0
|
|
|
|
|
|
$parent_node = $node; |
628
|
|
|
|
|
|
|
#Move to the next node |
629
|
0
|
|
|
|
|
|
$node = $node->[$CHILDS]{substr($key, $key_iter, 1)}; |
630
|
0
|
|
|
|
|
|
++$key_iter; |
631
|
|
|
|
|
|
|
} |
632
|
|
|
|
|
|
|
} |
633
|
|
|
|
|
|
|
|
634
|
0
|
|
|
|
|
|
my $pruned_trie; |
635
|
0
|
0
|
0
|
|
|
|
if ($node && $node->[$CHILDS]) { |
636
|
0
|
|
|
|
|
|
my $new_trie = []; |
637
|
0
|
|
|
|
|
|
$new_trie->[$CHILDS] = ${delete $node->[$CHILDS]}; |
|
0
|
|
|
|
|
|
|
638
|
0
|
|
|
|
|
|
$pruned_trie = $make_new_trie_ref->($new_trie); |
639
|
0
|
|
|
|
|
|
$compress_trie_ref->($parent_node, $grand_parent_node); |
640
|
|
|
|
|
|
|
} |
641
|
|
|
|
|
|
|
|
642
|
0
|
|
|
|
|
|
return $pruned_trie; |
643
|
|
|
|
|
|
|
} |
644
|
|
|
|
|
|
|
|
645
|
|
|
|
|
|
|
=head2 Trie Traversal |
646
|
|
|
|
|
|
|
|
647
|
|
|
|
|
|
|
=head3 all |
648
|
|
|
|
|
|
|
|
649
|
|
|
|
|
|
|
Get a list of every key and its associated value as [key => value] pairs. Order |
650
|
|
|
|
|
|
|
is not guaranteed. |
651
|
|
|
|
|
|
|
|
652
|
|
|
|
|
|
|
my @key_value_pairs = $trie->all; |
653
|
|
|
|
|
|
|
|
654
|
|
|
|
|
|
|
=cut |
655
|
|
|
|
|
|
|
|
656
|
|
|
|
|
|
|
sub all { |
657
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
658
|
|
|
|
|
|
|
|
659
|
0
|
|
|
|
|
|
my @results; |
660
|
0
|
|
|
|
|
|
for (my $iterator = $self->iterator; ! $iterator->is_done; $iterator->next) { |
661
|
0
|
|
|
|
|
|
push @results, [$iterator->key, $iterator->value]; |
662
|
|
|
|
|
|
|
} |
663
|
|
|
|
|
|
|
|
664
|
0
|
|
|
|
|
|
return @results; |
665
|
|
|
|
|
|
|
} |
666
|
|
|
|
|
|
|
|
667
|
|
|
|
|
|
|
=head3 iterator |
668
|
|
|
|
|
|
|
|
669
|
|
|
|
|
|
|
Get a Tree::SEMETrie::Iterator for efficient trie traversal. Order is not |
670
|
|
|
|
|
|
|
guaranteed. |
671
|
|
|
|
|
|
|
|
672
|
|
|
|
|
|
|
my $iterator = $trie->iterator; |
673
|
|
|
|
|
|
|
|
674
|
|
|
|
|
|
|
=cut |
675
|
|
|
|
|
|
|
|
676
|
0
|
|
|
0
|
1
|
|
sub iterator { Tree::SEMETrie::Iterator->new($_[0]) } |
677
|
|
|
|
|
|
|
|
678
|
|
|
|
|
|
|
=head1 AUTHOR |
679
|
|
|
|
|
|
|
|
680
|
|
|
|
|
|
|
Aaron Cohen, C<< >> |
681
|
|
|
|
|
|
|
|
682
|
|
|
|
|
|
|
=head1 BUGS |
683
|
|
|
|
|
|
|
|
684
|
|
|
|
|
|
|
Please report any bugs or feature requests to C, or through |
685
|
|
|
|
|
|
|
the web interface at L. I will be notified, and then you'll |
686
|
|
|
|
|
|
|
automatically be notified of progress on your bug as I make changes. |
687
|
|
|
|
|
|
|
|
688
|
|
|
|
|
|
|
=head1 TODO |
689
|
|
|
|
|
|
|
|
690
|
|
|
|
|
|
|
=over 4 |
691
|
|
|
|
|
|
|
|
692
|
|
|
|
|
|
|
=item * Finish SYNOPSIS section. |
693
|
|
|
|
|
|
|
|
694
|
|
|
|
|
|
|
=item * Finish merge function. |
695
|
|
|
|
|
|
|
|
696
|
|
|
|
|
|
|
=item * Finish prune function. |
697
|
|
|
|
|
|
|
|
698
|
|
|
|
|
|
|
=item * Add benchmarking scripts. |
699
|
|
|
|
|
|
|
|
700
|
|
|
|
|
|
|
=item * Add SEE ALSO section. |
701
|
|
|
|
|
|
|
|
702
|
|
|
|
|
|
|
=back |
703
|
|
|
|
|
|
|
|
704
|
|
|
|
|
|
|
=head1 SUPPORT |
705
|
|
|
|
|
|
|
|
706
|
|
|
|
|
|
|
You can find documentation for this module with the perldoc command. |
707
|
|
|
|
|
|
|
|
708
|
|
|
|
|
|
|
perldoc Tree::SEMETrie |
709
|
|
|
|
|
|
|
|
710
|
|
|
|
|
|
|
|
711
|
|
|
|
|
|
|
You can also look for information at: |
712
|
|
|
|
|
|
|
|
713
|
|
|
|
|
|
|
=over 4 |
714
|
|
|
|
|
|
|
|
715
|
|
|
|
|
|
|
=item * Official GitHub Repository |
716
|
|
|
|
|
|
|
|
717
|
|
|
|
|
|
|
L |
718
|
|
|
|
|
|
|
|
719
|
|
|
|
|
|
|
=item * RT: CPAN's request tracker (report bugs here) |
720
|
|
|
|
|
|
|
|
721
|
|
|
|
|
|
|
L |
722
|
|
|
|
|
|
|
|
723
|
|
|
|
|
|
|
=item * AnnoCPAN: Annotated CPAN documentation |
724
|
|
|
|
|
|
|
|
725
|
|
|
|
|
|
|
L |
726
|
|
|
|
|
|
|
|
727
|
|
|
|
|
|
|
=item * CPAN Ratings |
728
|
|
|
|
|
|
|
|
729
|
|
|
|
|
|
|
L |
730
|
|
|
|
|
|
|
|
731
|
|
|
|
|
|
|
=item * Search CPAN |
732
|
|
|
|
|
|
|
|
733
|
|
|
|
|
|
|
L |
734
|
|
|
|
|
|
|
|
735
|
|
|
|
|
|
|
=back |
736
|
|
|
|
|
|
|
|
737
|
|
|
|
|
|
|
=head1 LICENSE AND COPYRIGHT |
738
|
|
|
|
|
|
|
|
739
|
|
|
|
|
|
|
Copyright 2011 Aaron Cohen. |
740
|
|
|
|
|
|
|
|
741
|
|
|
|
|
|
|
This program is free software; you can redistribute it and/or modify it |
742
|
|
|
|
|
|
|
under the terms of either: the GNU General Public License as published |
743
|
|
|
|
|
|
|
by the Free Software Foundation; or the Artistic License. |
744
|
|
|
|
|
|
|
|
745
|
|
|
|
|
|
|
See http://dev.perl.org/licenses/ for more information. |
746
|
|
|
|
|
|
|
|
747
|
|
|
|
|
|
|
=cut |
748
|
|
|
|
|
|
|
|
749
|
|
|
|
|
|
|
1; # End of Tree::SEMETrie |