├── LICENSE
├── README.markdown
├── doc
├── btrie.html
├── edoc-info
├── erlang.png
├── index.html
├── modules-frame.html
├── overview-summary.html
├── stylesheet.css
└── trie.html
├── generate_docs.sh
├── mix.exs
├── rebar.config
├── src
├── btrie.erl
├── trie.app.src
├── trie.erl
├── trie.hrl
└── trie_test.hrl
└── test
├── proper_srv.erl
└── trie_proper.erl
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2010-2025 Michael Truog
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a
6 | copy of this software and associated documentation files (the "Software"),
7 | to deal in the Software without restriction, including without limitation
8 | the rights to use, copy, modify, merge, publish, distribute, sublicense,
9 | and/or sell copies of the Software, and to permit persons to whom the
10 | Software is furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21 | DEALINGS IN THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.markdown:
--------------------------------------------------------------------------------
1 | Erlang Trie Implementation
2 | ==========================
3 |
4 | The data structure is only for storing keys as strings (lists of integers), but is able to get performance close to the process dictionary when doing key lookups (based on [results here](http://okeuday.livejournal.com/20025.html) with [the benchmark here](http://github.com/okeuday/erlbench)). So, this data structure is (currently) the quickest for lookups on key-value pairs where all keys are strings, if you ignore the process dictionary (which many argue should never be used).
5 |
6 | The implementation stores leaf nodes as the string suffix because it is a [PATRICIA trie](https://xlinux.nist.gov/dads/HTML/patriciatree.html) (PATRICIA - Practical Algorithm to Retrieve Information Coded in Alphanumeric, D.R.Morrison (1968)). Storing leaf nodes this way helps avoid single child leafs (compressing the tree a little bit).
7 |
8 | The full OTP dict API is supported in addition to other functions. Functions like foldl, iter, itera, and foreach traverse in alphabetical order. Functions like map and foldr traverse in reverse alphabetical order. There are also functions like `find_prefix`, `is_prefix`, and `is_prefixed` that check if a prefix exists within the trie. The functions with a `"_similar"` suffix like `find_similar`, `foldl_similar`, and `foldr_similar` all operate with trie elements that share a common prefix with the supplied string.
9 |
10 | The trie data structure supports string patterns. The functions `find_match/2`, `fold_match/4`, and `pattern_parse/2` utilize patterns that contain a`"*"`wildcard character(s) (equivalent to ".+" regex while`"**"`is forbidden). The function `find_match/2` operates on a trie filled with patterns when supplied a string non-pattern, while the function `fold_match/4` operates on a trie without patterns when supplied a string pattern. The functions `find_match2/2` and `pattern2_parse/2` add `"?"` as an additional wildcard character (with `"**"`, `"??"`, `"*?"` and `"?*"` forbidden) that consumes greedily to the next character (`"?"` must not be the last character in the pattern).
11 |
12 | The btrie data structure was added because many people wanted a quick associative data structure for binary keys. However, other alternatives provide better efficiency, so the btrie is best used for functions that can not be found elsewhere (or perhaps extra-long keys)... more testing would be needed to determine the best use-cases of the btrie.
13 |
14 | Tests
15 | -----
16 |
17 | rebar compile
18 | ERL_LIBS="/path/to/proper" rebar eunit
19 |
20 | Author
21 | ------
22 |
23 | Michael Truog (mjtruog at protonmail dot com)
24 |
25 | License
26 | -------
27 |
28 | MIT License
29 |
--------------------------------------------------------------------------------
/doc/btrie.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Module btrie
6 |
7 |
8 |
9 |
10 |
11 |
12 | Module btrie
13 |
14 |
15 | The trie (i.e., from "retrieval") data structure was invented by
16 | Edward Fredkin (it is a form of radix sort). The implementation stores
17 | string suffixes as a list because it is a PATRICIA trie
18 | (PATRICIA - Practical Algorithm to Retrieve Information
19 | Coded in Alphanumeric, D.R.Morrison (1968)).
20 |
21 | This Erlang trie implementation uses binary keys.
22 | Copyright © 2010-2020 Michael Truog
23 |
24 | Version: 2.0.1 Oct 26 2023 11:28:28
25 | ------------------------------------------------------------------------
26 | Authors: Michael Truog (mjtruog at protonmail dot com ).
27 |
28 |
29 |
30 | The trie (i.e., from "retrieval") data structure was invented by
31 | Edward Fredkin (it is a form of radix sort). The implementation stores
32 | string suffixes as a list because it is a PATRICIA trie
33 | (PATRICIA - Practical Algorithm to Retrieve Information
34 | Coded in Alphanumeric, D.R.Morrison (1968)).
35 |
36 | This Erlang trie implementation uses binary keys. Using binary keys
37 | means that other data structures are quicker alternatives, so this
38 | module is probably not a good choice, unless it is used for functions
39 | not available elsewhere.
40 |
41 |
42 |
43 | empty_trie() = <<>>
44 |
45 |
46 |
47 | nonempty_trie() = {integer(), integer(), tuple()}
48 |
49 |
50 |
51 | trie() = nonempty_trie() | empty_trie()
52 |
53 |
54 |
55 | append/3
56 | .
57 | append_list/3
58 | .
59 | erase/2
60 | .
61 | erase_similar/2
62 | .
63 | fetch/2
64 | .
65 | fetch_keys/1
66 | .
67 | fetch_keys_similar/2
68 | .
69 | filter/2
70 | .
71 | find/2
72 | .
73 | find_prefix/2
74 |
75 | The atom 'prefix' is returned if the string supplied is a prefix
76 | for a key that has previously been stored within the trie, but no
77 | value was found, since there was no exact match for the string supplied.
78 | find_prefix_longest/2
79 | .
80 | find_prefixes/2
81 |
82 | The entries are returned in alphabetical order.
83 | fold/3
84 |
85 | Traverses in alphabetical order.
86 | fold_similar/4
87 |
88 | Traverses in alphabetical order.
89 | foldl/3
90 |
91 | Traverses in alphabetical order.
92 | foldl_similar/4
93 |
94 | Traverses in alphabetical order.
95 | foldr/3
96 |
97 | Traverses in reverse alphabetical order.
98 | foldr_similar/4
99 |
100 | Traverses in reverse alphabetical order.
101 | foreach/2
102 |
103 | Traverses in alphabetical order.
104 | from_list/1
105 | .
106 | is_key/2
107 | .
108 | map/2
109 |
110 | Traverses in reverse alphabetical order.
111 | merge/3
112 |
113 | Update the second trie parameter with all of the elements
114 | found within the first trie parameter.
115 | new/0
116 | .
117 | new/1
118 |
119 | The list may contain either: strings, 2 element tuples with a string as the
120 | first tuple element, or tuples with more than 2 elements (including records)
121 | with a string as the first element (second element if it is a record).
122 | prefix/3
123 |
124 | The reverse of append/3.
125 | size/1
126 | .
127 | store/2
128 | .
129 | store/3
130 | .
131 | take/2
132 | .
133 | to_list/1
134 |
135 | The list is in alphabetical order.
136 | to_list_similar/2
137 | .
138 | update/3
139 | .
140 | update/4
141 | .
142 | update_counter/3
143 | .
144 |
145 |
146 |
147 |
148 |
149 |
153 |
154 |
155 |
156 |
157 |
161 |
162 |
163 |
164 |
165 |
166 |
erase(Key::<<_:8, _:_*8>>, Node::trie() ) -> trie()
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
erase_similar(Similar::<<_:8, _:_*8>>, Node::trie() ) -> [<<_:8, _:_*8>>]
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
fetch(X1::<<_:8, _:_*8>>, Node::nonempty_trie() ) -> any()
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
fetch_keys(Node::trie() ) -> [<<_:8, _:_*8>>]
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
fetch_keys_similar(Similar::<<_:8, _:_*8>>, Node::trie() ) -> [<<_:8, _:_*8>>]
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
filter(F::fun((<<_:8, _:_*8>>, any()) -> boolean()), Node::trie() ) -> trie()
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
find(X1::<<_:8, _:_*8>>, Node::trie() ) -> {ok, any()} | error
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
find_prefix(X1::<<_:8, _:_*8>>, X2::trie() ) -> {ok, any()} | prefix | error
223 |
224 |
225 |
226 | The atom 'prefix' is returned if the string supplied is a prefix
227 | for a key that has previously been stored within the trie, but no
228 | value was found, since there was no exact match for the string supplied.
229 |
230 |
231 |
232 |
find_prefix_longest(Match::<<_:8, _:_*8>>, Node::trie() ) -> {ok, <<_:8, _:_*8>>, any()} | error
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
find_prefixes(Match::<<_:8, _:_*8>>, Node::trie() ) -> [{<<_:8, _:_*8>>, any()}]
241 |
242 |
243 |
244 | The entries are returned in alphabetical order.
245 |
246 |
247 |
248 |
fold(F::fun((<<_:8, _:_*8>>, any(), any()) -> any()), A::any(), Node::trie() ) -> any()
249 |
250 |
251 |
252 | Traverses in alphabetical order.
253 |
254 |
255 |
256 |
fold_similar(Similar::<<_:8, _:_*8>>, F::fun((<<_:8, _:_*8>>, any(), any()) -> any()), A::any(), Node::trie() ) -> any()
257 |
258 |
259 |
260 | Traverses in alphabetical order.
261 |
262 |
263 |
264 |
foldl(F::fun((<<_:8, _:_*8>>, any(), any()) -> any()), A::any(), Node::trie() ) -> any()
265 |
266 |
267 |
268 | Traverses in alphabetical order.
269 |
270 |
271 |
272 |
foldl_similar(Similar::<<_:8, _:_*8>>, F::fun((<<_:8, _:_*8>>, any(), any()) -> any()), A::any(), Node::trie() ) -> any()
273 |
274 |
275 |
276 | Traverses in alphabetical order.
277 |
278 |
279 |
280 |
foldr(F::fun((<<_:8, _:_*8>>, any(), any()) -> any()), A::any(), Node::trie() ) -> any()
281 |
282 |
283 |
284 | Traverses in reverse alphabetical order.
285 |
286 |
287 |
288 |
foldr_similar(Similar::<<_:8, _:_*8>>, F::fun((<<_:8, _:_*8>>, any(), any()) -> any()), A::any(), Node::trie() ) -> any()
289 |
290 |
291 |
292 | Traverses in reverse alphabetical order.
293 |
294 |
295 |
296 |
foreach(F::fun((<<_:8, _:_*8>>, any()) -> any()), Node::trie() ) -> any()
297 |
298 |
299 |
300 | Traverses in alphabetical order.
301 |
302 |
303 |
304 |
from_list(L::[<<_:8, _:_*8>> | tuple()]) -> trie()
305 |
306 |
307 |
308 |
309 |
310 |
311 |
312 |
is_key(X1::<<_:8, _:_*8>>, Node::trie() ) -> boolean()
313 |
314 |
315 |
316 |
317 |
318 |
319 |
320 |
map(F::fun((<<_:8, _:_*8>>, any()) -> any()), Node::trie() ) -> trie()
321 |
322 |
323 |
324 | Traverses in reverse alphabetical order.
325 |
326 |
327 |
328 |
merge(F::fun((<<_:8, _:_*8>>, any(), any()) -> any()), Node1::trie() , Node2::trie() ) -> trie()
329 |
330 |
331 |
332 | Update the second trie parameter with all of the elements
333 | found within the first trie parameter.
334 |
335 |
336 |
340 |
341 |
342 |
343 |
344 |
345 |
new(L::[<<_:8, _:_*8>> | tuple()]) -> trie()
346 |
347 |
348 |
349 | The list may contain either: strings, 2 element tuples with a string as the
350 | first tuple element, or tuples with more than 2 elements (including records)
351 | with a string as the first element (second element if it is a record).
352 | If a list of records (or tuples larger than 2 elements) is provided,
353 | the whole record/tuple is stored as the value.
354 |
355 |
356 |
360 |
361 | The reverse of append/3.
362 |
363 |
364 |
365 |
size(Node::trie() ) -> non_neg_integer()
366 |
367 |
368 |
369 |
370 |
371 |
372 |
376 |
377 |
378 |
379 |
380 |
384 |
385 |
386 |
387 |
388 |
389 |
take(Key::<<_:8, _:_*8>>, Node::trie() ) -> {any(), trie() } | error
390 |
391 |
392 |
393 |
394 |
395 |
396 |
397 |
to_list(Node::trie() ) -> [{<<_:8, _:_*8>>, any()}]
398 |
399 |
400 |
401 | The list is in alphabetical order.
402 |
403 |
404 |
405 |
to_list_similar(Similar::<<_:8, _:_*8>>, Node::trie() ) -> [{<<_:8, _:_*8>>, any()}]
406 |
407 |
408 |
409 |
410 |
411 |
412 |
416 |
417 |
418 |
419 |
420 |
421 |
update(Key::<<_:8, _:_*8>>, F::fun((any()) -> any()), Initial::any(), Node::trie() ) -> nonempty_trie()
422 |
423 |
424 |
425 |
426 |
427 |
428 |
429 |
update_counter(Key::<<_:8, _:_*8>>, Increment::number(), Node::trie() ) -> nonempty_trie()
430 |
431 |
432 |
433 |
434 |
435 |
436 |
437 | Generated by EDoc
438 |
439 |
440 |
--------------------------------------------------------------------------------
/doc/edoc-info:
--------------------------------------------------------------------------------
1 | %% encoding: UTF-8
2 | {application,trie}.
3 | {modules,[btrie,trie]}.
4 |
--------------------------------------------------------------------------------
/doc/erlang.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/okeuday/trie/0cf9c913b5e0f39a46c763a2168077d2c4dfd0e9/doc/erlang.png
--------------------------------------------------------------------------------
/doc/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The trie application
5 |
6 |
7 |
8 |
9 |
10 |
11 | This page uses frames
12 | Your browser does not accept frames.
13 | You should go to the non-frame version instead.
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/doc/modules-frame.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The trie application
5 |
6 |
7 |
8 | Modules
9 |
12 |
13 |
--------------------------------------------------------------------------------
/doc/overview-summary.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | The trie application
6 |
7 |
8 |
9 |
10 | The trie application
11 |
12 |
13 |
14 | Generated by EDoc
15 |
16 |
17 |
--------------------------------------------------------------------------------
/doc/stylesheet.css:
--------------------------------------------------------------------------------
1 | /* standard EDoc style sheet */
2 | body {
3 | font-family: Verdana, Arial, Helvetica, sans-serif;
4 | margin-left: .25in;
5 | margin-right: .2in;
6 | margin-top: 0.2in;
7 | margin-bottom: 0.2in;
8 | color: #000000;
9 | background-color: #ffffff;
10 | }
11 | h1,h2 {
12 | margin-left: -0.2in;
13 | }
14 | div.navbar {
15 | background-color: #add8e6;
16 | padding: 0.2em;
17 | }
18 | h2.indextitle {
19 | padding: 0.4em;
20 | background-color: #add8e6;
21 | }
22 | h3.function,h3.typedecl {
23 | background-color: #add8e6;
24 | padding-left: 1em;
25 | }
26 | div.spec {
27 | margin-left: 2em;
28 | background-color: #eeeeee;
29 | }
30 | a.module {
31 | text-decoration:none
32 | }
33 | a.module:hover {
34 | background-color: #eeeeee;
35 | }
36 | ul.definitions {
37 | list-style-type: none;
38 | }
39 | ul.index {
40 | list-style-type: none;
41 | background-color: #eeeeee;
42 | }
43 |
44 | /*
45 | * Minor style tweaks
46 | */
47 | ul {
48 | list-style-type: square;
49 | }
50 | table {
51 | border-collapse: collapse;
52 | }
53 | td {
54 | padding: 3
55 | }
56 |
--------------------------------------------------------------------------------
/doc/trie.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Module trie
6 |
7 |
8 |
9 |
10 |
11 |
12 | Module trie
13 |
14 |
15 | The trie (i.e., from "retrieval") data structure was invented by
16 | Edward Fredkin (it is a form of radix sort). The implementation stores
17 | string suffixes as a list because it is a PATRICIA trie
18 | (PATRICIA - Practical Algorithm to Retrieve Information
19 | Coded in Alphanumeric, D.R.Morrison (1968)).
20 |
21 | This Erlang trie implementation uses string (list of integers) keys and
22 | is able to get performance close to the process dictionary when doing key
23 | lookups (find or fetch, see http://okeuday.livejournal.com/16941.html ).
24 | Copyright © 2010-2022 Michael Truog
25 |
26 | Version: 2.0.5 Oct 26 2023 11:28:28
27 | ------------------------------------------------------------------------
28 | Authors: Michael Truog (mjtruog at protonmail dot com ).
29 |
30 |
31 |
32 | The trie (i.e., from "retrieval") data structure was invented by
33 | Edward Fredkin (it is a form of radix sort). The implementation stores
34 | string suffixes as a list because it is a PATRICIA trie
35 | (PATRICIA - Practical Algorithm to Retrieve Information
36 | Coded in Alphanumeric, D.R.Morrison (1968)).
37 |
38 | This Erlang trie implementation uses string (list of integers) keys and
39 | is able to get performance close to the process dictionary when doing key
40 | lookups (find or fetch, see http://okeuday.livejournal.com/16941.html ).
41 | Utilizing this trie, it is possible to avoid generating dynamic atoms
42 | in various contexts. Also, an added benefit to using this trie is that
43 | the traversals preserve alphabetical ordering.
44 |
45 |
46 |
47 | empty_trie() = []
48 |
49 |
50 |
51 | nonempty_trie() = {integer(), integer(), tuple()}
52 |
53 |
54 |
55 | trie() = nonempty_trie() | empty_trie()
56 |
57 |
58 |
59 | append/3
60 | .
61 | append_list/3
62 | .
63 | erase/2
64 | .
65 | erase_similar/2
66 | .
67 | fetch/2
68 | .
69 | fetch_keys/1
70 | .
71 | fetch_keys_similar/2
72 | .
73 | filter/2
74 | .
75 | find/2
76 | .
77 | find_match/2
78 |
79 | All patterns held within the trie use a wildcard character "*" to represent
80 | a regex of ".+".
81 | find_match2/2
82 |
83 | All patterns held within the trie use the wildcard character "*" or "?"
84 | to represent a regex of ".+".
85 | find_prefix/2
86 |
87 | The atom 'prefix' is returned if the string supplied is a prefix
88 | for a key that has previously been stored within the trie, but no
89 | value was found, since there was no exact match for the string supplied.
90 | find_prefix_longest/2
91 | .
92 | find_prefixes/2
93 |
94 | The entries are returned in alphabetical order.
95 | find_similar/2
96 |
97 | The first match is found based on alphabetical order.
98 | fold/3
99 |
100 | Traverses in alphabetical order.
101 | fold_match/4
102 |
103 | Traverses in alphabetical order.
104 | fold_similar/4
105 |
106 | Traverses in alphabetical order.
107 | foldl/3
108 |
109 | Traverses in alphabetical order.
110 | foldl_similar/4
111 |
112 | Traverses in alphabetical order.
113 | foldr/3
114 |
115 | Traverses in reverse alphabetical order.
116 | foldr_similar/4
117 |
118 | Traverses in reverse alphabetical order.
119 | foreach/2
120 |
121 | Traverses in alphabetical order.
122 | from_list/1
123 | .
124 | is_bytestring/1
125 | .
126 | is_bytestring_nonempty/1
127 | .
128 | is_key/2
129 | .
130 | is_pattern/1
131 |
132 | "*" is the wildcard character (equivalent to the ".+" regex).
133 | is_pattern2/1
134 |
135 | "*" and "?" are wildcard characters (equivalent to the ".+" regex).
136 | is_pattern2_bytes/1
137 |
138 | "*" and "?" are wildcard characters (equivalent to the ".+" regex).
139 | is_pattern_bytes/1
140 |
141 | "*" is the wildcard character (equivalent to the ".+" regex).
142 | is_prefix/2
143 |
144 | The function returns true if the string supplied is a prefix
145 | for a key that has previously been stored within the trie.
146 | is_prefixed/2
147 | .
148 | is_prefixed/3
149 |
150 | The prefix within the trie must match at least 1 character that is not
151 | within the excluded list of characters.
152 | iter/2
153 |
154 | Traverses in alphabetical order.
155 | itera/3
156 |
157 | Traverses in alphabetical order.
158 | map/2
159 |
160 | Traverses in reverse alphabetical order.
161 | merge/3
162 |
163 | Update the second trie parameter with all of the elements
164 | found within the first trie parameter.
165 | new/0
166 | .
167 | new/1
168 |
169 | The list may contain either: strings, 2 element tuples with a string as the
170 | first tuple element, or tuples with more than 2 elements (including records)
171 | with a string as the first element (second element if it is a record).
172 | pattern2_fill/2
173 |
174 | The "*" and "?" wildcard characters may be used consecutively by this
175 | function to have parameters concatenated (both are processed the same
176 | way by this function).
177 | pattern2_fill/4
178 |
179 | The "*" and "?" wildcard characters may be used consecutively by this
180 | function to have parameters concatenated (both are processed the same
181 | way by this function).
182 | pattern2_parse/2
183 |
184 | "*" and "?" are wildcard characters (equivalent to the ".+" regex).
185 | pattern2_parse/3
186 |
187 | "*" and "?" are wildcard characters (equivalent to the ".+" regex).
188 | pattern2_suffix/2
189 |
190 | "*" and "?" are wildcard characters (equivalent to the ".+" regex).
191 | pattern_fill/2
192 |
193 | The "*" wildcard character may be used consecutively by this function
194 | to have parameters concatenated.
195 | pattern_fill/4
196 |
197 | The "*" wildcard character may be used consecutively by this function
198 | to have parameters concatenated.
199 | pattern_parse/2
200 |
201 | "*" is the wildcard character (equivalent to the ".+" regex).
202 | pattern_parse/3
203 |
204 | "*" is the wildcard character (equivalent to the ".+" regex).
205 | pattern_suffix/2
206 |
207 | "*" is the wildcard character (equivalent to the ".+" regex).
208 | prefix/3
209 |
210 | The reverse of append/3.
211 | size/1
212 | .
213 | store/2
214 | .
215 | store/3
216 | .
217 | take/2
218 | .
219 | to_list/1
220 |
221 | The list is in alphabetical order.
222 | to_list_similar/2
223 | .
224 | update/3
225 | .
226 | update/4
227 | .
228 | update_counter/3
229 | .
230 |
231 |
232 |
233 |
234 |
235 |
239 |
240 |
241 |
242 |
243 |
244 |
append_list(Key::nonempty_string(), ValueList::list(), Node::trie() ) -> nonempty_trie()
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
erase(Key::nonempty_string(), Node::trie() ) -> trie()
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
erase_similar(Similar::nonempty_string(), Node::trie() ) -> [nonempty_string()]
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
fetch(T::nonempty_string(), Node::nonempty_trie() ) -> any()
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
fetch_keys(Node::trie() ) -> [nonempty_string()]
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
fetch_keys_similar(Similar::nonempty_string(), Node::trie() ) -> [nonempty_string()]
285 |
286 |
287 |
288 |
289 |
290 |
291 |
292 |
filter(F::fun((nonempty_string(), any()) -> boolean()), Node::trie() ) -> trie()
293 |
294 |
295 |
296 |
297 |
298 |
299 |
300 |
find(T::nonempty_string(), Node::trie() ) -> {ok, any()} | error
301 |
302 |
303 |
304 |
305 |
306 |
307 |
308 |
find_match(Match::string(), Node::trie() ) -> {ok, any(), any()} | error
309 |
310 |
311 |
312 | All patterns held within the trie use a wildcard character "*" to represent
313 | a regex of ".+". "**" within the trie will result in undefined behavior
314 | (the pattern is malformed). The function will search for the most specific
315 | match possible, given the input string and the trie contents. The input
316 | string must not contain wildcard characters, otherwise a badarg exit
317 | exception will occur. If you instead want to supply a pattern string to
318 | match the contents of the trie, see fold_match/4.
319 |
320 |
321 |
322 |
find_match2(Match::string(), Node::trie() ) -> {ok, any(), any()} | error
323 |
324 |
325 |
326 | All patterns held within the trie use the wildcard character "*" or "?"
327 | to represent a regex of ".+". "**", "??", "*?", or "?*" within the
328 | trie will result in undefined behavior (the pattern is malformed).
329 | The function will search for the most specific match possible, given the
330 | input string and the trie contents. The input string must not contain
331 | wildcard characters, otherwise a badarg exit exception will occur.
332 | The "?" wildcard character consumes the shortest match to the next
333 | character and must not be the the last character in the string
334 | (the pattern would be malformed).
335 |
336 |
337 |
338 |
find_prefix(T::nonempty_string(), X2::trie() ) -> {ok, any()} | prefix | error
339 |
340 |
341 |
342 | The atom 'prefix' is returned if the string supplied is a prefix
343 | for a key that has previously been stored within the trie, but no
344 | value was found, since there was no exact match for the string supplied.
345 |
346 |
347 |
348 |
find_prefix_longest(Match::nonempty_string(), Node::trie() ) -> {ok, nonempty_string(), any()} | error
349 |
350 |
351 |
352 |
353 |
354 |
355 |
356 |
find_prefixes(Match::nonempty_string(), Node::trie() ) -> [{nonempty_string(), any()}]
357 |
358 |
359 |
360 | The entries are returned in alphabetical order.
361 |
362 |
363 |
364 |
find_similar(Similar::string(), Node::trie() ) -> {ok, string(), any()} | error
365 |
366 |
367 |
368 | The first match is found based on alphabetical order.
369 |
370 |
371 |
372 |
fold(F::fun((nonempty_string(), any(), any()) -> any()), A::any(), Node::trie() ) -> any()
373 |
374 |
375 |
376 | Traverses in alphabetical order.
377 |
378 |
379 |
380 |
fold_match(Match::string(), F::fun((string(), any(), any()) -> any()), A::any(), Node::trie() ) -> any()
381 |
382 |
383 |
384 | Traverses in alphabetical order. Uses "*" as a wildcard character
385 | within the pattern (it acts like a ".+" regex, and "**" is forbidden).
386 | The trie keys must not contain wildcard characters, otherwise a badarg
387 | exit exception will occur. If you want to match a specific string
388 | without wildcards on trie values that contain wildcard characters,
389 | see find_match/2.
390 |
391 |
392 |
393 |
fold_similar(Similar::nonempty_string(), F::fun((nonempty_string(), any(), any()) -> any()), A::any(), Node::trie() ) -> any()
394 |
395 |
396 |
397 | Traverses in alphabetical order.
398 |
399 |
400 |
401 |
foldl(F::fun((nonempty_string(), any(), any()) -> any()), A::any(), Node::trie() ) -> any()
402 |
403 |
404 |
405 | Traverses in alphabetical order.
406 |
407 |
408 |
409 |
foldl_similar(Similar::nonempty_string(), F::fun((nonempty_string(), any(), any()) -> any()), A::any(), Node::trie() ) -> any()
410 |
411 |
412 |
413 | Traverses in alphabetical order.
414 |
415 |
416 |
417 |
foldr(F::fun((nonempty_string(), any(), any()) -> any()), A::any(), Node::trie() ) -> any()
418 |
419 |
420 |
421 | Traverses in reverse alphabetical order.
422 |
423 |
424 |
425 |
foldr_similar(Similar::nonempty_string(), F::fun((nonempty_string(), any(), any()) -> any()), A::any(), Node::trie() ) -> any()
426 |
427 |
428 |
429 | Traverses in reverse alphabetical order.
430 |
431 |
432 |
433 |
foreach(F::fun((nonempty_string(), any()) -> any()), Node::trie() ) -> any()
434 |
435 |
436 |
437 | Traverses in alphabetical order.
438 |
439 |
440 |
441 |
from_list(L::[nonempty_string() | tuple()]) -> trie()
442 |
443 |
444 |
445 |
446 |
447 |
448 |
449 |
is_bytestring(L::[byte()]) -> true | false
450 |
451 |
452 |
453 |
454 |
455 |
456 |
457 |
is_bytestring_nonempty(L::[byte(), ...]) -> true | false
458 |
459 |
460 |
461 |
462 |
463 |
464 |
465 |
is_key(T::nonempty_string(), Node::trie() ) -> boolean()
466 |
467 |
468 |
469 |
470 |
471 |
472 |
473 |
is_pattern(Pattern::string()) -> true | false
474 |
475 |
476 |
477 | "*" is the wildcard character (equivalent to the ".+" regex).
478 | "**" is forbidden.
479 |
480 |
481 |
482 |
is_pattern2(Pattern::string()) -> true | false
483 |
484 |
485 |
486 | "*" and "?" are wildcard characters (equivalent to the ".+" regex).
487 | "**", "??", "*?" and "?*" are forbidden. "?" must not be the last
488 | character in the pattern.
489 |
490 |
491 |
492 |
is_pattern2_bytes(Pattern::[byte()]) -> true | false
493 |
494 |
495 |
496 | "*" and "?" are wildcard characters (equivalent to the ".+" regex).
497 | "**", "??", "*?" and "?*" are forbidden. "?" must not be the last
498 | character in the pattern.
499 |
500 |
501 |
502 |
is_pattern_bytes(Pattern::[byte()]) -> true | false
503 |
504 |
505 |
506 | "*" is the wildcard character (equivalent to the ".+" regex).
507 | "**" is forbidden.
508 |
509 |
510 |
511 |
is_prefix(T::string(), X2::trie() ) -> true | false
512 |
513 |
514 |
515 | The function returns true if the string supplied is a prefix
516 | for a key that has previously been stored within the trie.
517 | If no values with the prefix matching key(s) were removed from the trie,
518 | then the prefix currently exists within the trie.
519 |
520 |
521 |
522 |
is_prefixed(T::string(), X2::trie() ) -> true | false
523 |
524 |
525 |
526 |
527 |
528 |
529 |
530 |
is_prefixed(Key::string(), Exclude::string(), Node::trie() ) -> true | false
531 |
532 |
533 |
534 | The prefix within the trie must match at least 1 character that is not
535 | within the excluded list of characters.
536 |
537 |
538 |
539 |
iter(F::fun((string(), any(), fun(() -> any())) -> any()), Node::trie() ) -> ok
540 |
541 |
542 |
543 | Traverses in alphabetical order.
544 |
545 |
546 |
547 |
itera(F::fun((string(), any(), any(), fun((any()) -> any())) -> any()), A::any(), Node::trie() ) -> any()
548 |
549 |
550 |
551 | Traverses in alphabetical order.
552 |
553 |
554 |
555 |
map(F::fun((nonempty_string(), any()) -> any()), Node::trie() ) -> trie()
556 |
557 |
558 |
559 | Traverses in reverse alphabetical order.
560 |
561 |
562 |
563 |
merge(F::fun((nonempty_string(), any(), any()) -> any()), Node1::trie() , Node2::trie() ) -> trie()
564 |
565 |
566 |
567 | Update the second trie parameter with all of the elements
568 | found within the first trie parameter.
569 |
570 |
571 |
575 |
576 |
577 |
578 |
579 |
580 |
new(L::[nonempty_string() | tuple()]) -> trie()
581 |
582 |
583 |
584 | The list may contain either: strings, 2 element tuples with a string as the
585 | first tuple element, or tuples with more than 2 elements (including records)
586 | with a string as the first element (second element if it is a record).
587 | If a list of records (or tuples larger than 2 elements) is provided,
588 | the whole record/tuple is stored as the value.
589 |
590 |
591 |
592 |
pattern2_fill(FillPattern::string(), Parameters::[string()]) -> {ok, string()} | {error, parameters_ignored | parameter_missing}
593 |
594 |
595 |
596 | The "*" and "?" wildcard characters may be used consecutively by this
597 | function to have parameters concatenated (both are processed the same
598 | way by this function).
599 |
600 |
601 |
602 |
pattern2_fill(FillPattern::string(), Parameters::[string()], ParametersSelected::[pos_integer()], ParametersStrictMatching::boolean()) -> {ok, string()} | {error, parameters_ignored | parameter_missing | parameters_selected_empty | {parameters_selected_ignored, [pos_integer()]} | {parameters_selected_missing, pos_integer()}}
603 |
604 |
605 |
606 | The "*" and "?" wildcard characters may be used consecutively by this
607 | function to have parameters concatenated (both are processed the same
608 | way by this function).
609 |
610 |
611 |
612 |
pattern2_parse(Pattern::string(), L::string()) -> [string()] | error
613 |
614 |
615 |
616 | "*" and "?" are wildcard characters (equivalent to the ".+" regex).
617 | "**", "??", "*?" and "?*" are forbidden. "?" must not be the last
618 | character in the pattern.
619 |
620 |
621 |
622 |
pattern2_parse(Pattern::string(), L::string(), Option::default | with_suffix | expanded) -> [string()] | {[string()], string()} | [string() | {exact, string()}] | error
623 |
624 |
625 |
626 | "*" and "?" are wildcard characters (equivalent to the ".+" regex).
627 | "**", "??", "*?" and "?*" are forbidden. "?" must not be the last
628 | character in the pattern.
629 |
630 |
631 |
632 |
pattern2_suffix(Pattern::string(), L::string()) -> string() | error
633 |
634 |
635 |
636 | "*" and "?" are wildcard characters (equivalent to the ".+" regex).
637 | "**", "??", "*?" and "?*" are forbidden. "?" must not be the last
638 | character in the pattern.
639 |
640 |
641 |
642 |
pattern_fill(FillPattern::string(), Parameters::[string()]) -> {ok, string()} | {error, parameters_ignored | parameter_missing}
643 |
644 |
645 |
646 | The "*" wildcard character may be used consecutively by this function
647 | to have parameters concatenated.
648 |
649 |
650 |
651 |
pattern_fill(FillPattern::string(), Parameters::[string()], ParametersSelected::[pos_integer()], ParametersStrictMatching::boolean()) -> {ok, string()} | {error, parameters_ignored | parameter_missing | parameters_selected_empty | {parameters_selected_ignored, [pos_integer()]} | {parameters_selected_missing, pos_integer()}}
652 |
653 |
654 |
655 | The "*" wildcard character may be used consecutively by this function
656 | to have parameters concatenated.
657 |
658 |
659 |
660 |
pattern_parse(Pattern::string(), L::string()) -> [string()] | error
661 |
662 |
663 |
664 | "*" is the wildcard character (equivalent to the ".+" regex).
665 | "**" is forbidden.
666 |
667 |
668 |
669 |
pattern_parse(Pattern::string(), L::string(), Option::default | with_suffix | expanded) -> [string()] | {[string()], string()} | [string() | {exact, string()}] | error
670 |
671 |
672 |
673 | "*" is the wildcard character (equivalent to the ".+" regex).
674 | "**" is forbidden.
675 |
676 |
677 |
678 |
pattern_suffix(Pattern::string(), L::string()) -> string() | error
679 |
680 |
681 |
682 | "*" is the wildcard character (equivalent to the ".+" regex).
683 | "**" is forbidden.
684 |
685 |
686 |
690 |
691 | The reverse of append/3.
692 |
693 |
694 |
695 |
size(Node::trie() ) -> non_neg_integer()
696 |
697 |
698 |
699 |
700 |
701 |
702 |
706 |
707 |
708 |
709 |
710 |
714 |
715 |
716 |
717 |
718 |
719 |
take(Key::nonempty_string(), Node::trie() ) -> {any(), trie() } | error
720 |
721 |
722 |
723 |
724 |
725 |
726 |
727 |
to_list(Node::trie() ) -> [{nonempty_string(), any()}]
728 |
729 |
730 |
731 | The list is in alphabetical order.
732 |
733 |
734 |
735 |
to_list_similar(Similar::nonempty_string(), Node::trie() ) -> [{nonempty_string(), any()}]
736 |
737 |
738 |
739 |
740 |
741 |
742 |
746 |
747 |
748 |
749 |
750 |
751 |
update(Key::nonempty_string(), F::fun((any()) -> any()), Initial::any(), Node::trie() ) -> nonempty_trie()
752 |
753 |
754 |
755 |
756 |
757 |
758 |
759 |
update_counter(Key::nonempty_string(), Increment::number(), Node::trie() ) -> nonempty_trie()
760 |
761 |
762 |
763 |
764 |
765 |
766 |
767 | Generated by EDoc
768 |
769 |
770 |
--------------------------------------------------------------------------------
/generate_docs.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | FILES_CHANGED=`git status --porcelain 2>/dev/null| grep "^?? src\/" | wc -l`
3 | if [ ${FILES_CHANGED} -ne 0 ]; then
4 | echo "Commit changes!"
5 | exit 1
6 | fi
7 | sed -e "/-include(\"trie.hrl\")\./ r src/trie.hrl" \
8 | -e "/-include(\"trie.hrl\")\./d" \
9 | src/trie.erl > src/trie.erl.doc
10 | sed -e "/-include(\"trie.hrl\")\./ r src/trie.hrl" \
11 | -e "/-include(\"trie.hrl\")\./d" \
12 | src/btrie.erl > src/btrie.erl.doc
13 | mv src/trie.erl.doc src/trie.erl
14 | mv src/btrie.erl.doc src/btrie.erl
15 | rebar doc
16 | git checkout src
17 |
18 |
--------------------------------------------------------------------------------
/mix.exs:
--------------------------------------------------------------------------------
1 | #-*-Mode:elixir;coding:utf-8;tab-width:2;c-basic-offset:2;indent-tabs-mode:()-*-
2 | # ex: set ft=elixir fenc=utf-8 sts=2 ts=2 sw=2 et nomod:
3 |
4 | defmodule Trie.Mixfile do
5 | use Mix.Project
6 |
7 | def project do
8 | [app: :trie,
9 | version: "2.0.7",
10 | language: :erlang,
11 | erlc_options: [
12 | :deterministic,
13 | :debug_info,
14 | :warn_export_vars,
15 | :warn_unused_import,
16 | #:warn_missing_spec,
17 | :warnings_as_errors],
18 | description: description(),
19 | package: package(),
20 | deps: deps()]
21 | end
22 |
23 | defp deps do
24 | []
25 | end
26 |
27 | defp description do
28 | "Erlang Trie Implementation"
29 | end
30 |
31 | defp package do
32 | [files: ~w(src doc test rebar.config README.markdown LICENSE),
33 | maintainers: ["Michael Truog"],
34 | licenses: ["MIT"],
35 | links: %{"GitHub" => "https://github.com/okeuday/trie"}]
36 | end
37 | end
38 |
--------------------------------------------------------------------------------
/rebar.config:
--------------------------------------------------------------------------------
1 | %-*-Mode:erlang;coding:utf-8;tab-width:4;c-basic-offset:4;indent-tabs-mode:()-*-
2 | % ex: set ft=erlang fenc=utf-8 sts=4 ts=4 sw=4 et nomod:
3 |
4 | {erl_opts,
5 | [warn_export_vars,
6 | warn_unused_import,
7 | %warn_missing_spec,
8 | warnings_as_errors]}.
9 | {edoc_opts, [{preprocess, true}]}.
10 | {cover_enabled, true}.
11 | {cover_print_enabled, true}.
12 | {cover_export_enabled, true}.
13 |
--------------------------------------------------------------------------------
/src/btrie.erl:
--------------------------------------------------------------------------------
1 | %-*-Mode:erlang;coding:utf-8;tab-width:4;c-basic-offset:4;indent-tabs-mode:()-*-
2 | % ex: set ft=erlang fenc=utf-8 sts=4 ts=4 sw=4 et nomod:
3 | %%%
4 | %%%------------------------------------------------------------------------
5 | %%% @doc
6 | %%% ==A trie data structure implementation.==
7 | %%% The trie (i.e., from "retrieval") data structure was invented by
8 | %%% Edward Fredkin (it is a form of radix sort). The implementation stores
9 | %%% string suffixes as a list because it is a PATRICIA trie
10 | %%% (PATRICIA - Practical Algorithm to Retrieve Information
11 | %%% Coded in Alphanumeric, D.R.Morrison (1968)).
12 | %%%
13 | %%% This Erlang trie implementation uses binary keys. Using binary keys
14 | %%% means that other data structures are quicker alternatives, so this
15 | %%% module is probably not a good choice, unless it is used for functions
16 | %%% not available elsewhere.
17 | %%% @end
18 | %%%
19 | %%% MIT License
20 | %%%
21 | %%% Copyright (c) 2010-2025 Michael Truog
22 | %%%
23 | %%% Permission is hereby granted, free of charge, to any person obtaining a
24 | %%% copy of this software and associated documentation files (the "Software"),
25 | %%% to deal in the Software without restriction, including without limitation
26 | %%% the rights to use, copy, modify, merge, publish, distribute, sublicense,
27 | %%% and/or sell copies of the Software, and to permit persons to whom the
28 | %%% Software is furnished to do so, subject to the following conditions:
29 | %%%
30 | %%% The above copyright notice and this permission notice shall be included in
31 | %%% all copies or substantial portions of the Software.
32 | %%%
33 | %%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
34 | %%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
35 | %%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
36 | %%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
37 | %%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
38 | %%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
39 | %%% DEALINGS IN THE SOFTWARE.
40 | %%%
41 | %%% @author Michael Truog
42 | %%% @copyright 2010-2025 Michael Truog
43 | %%% @version 2.0.8 {@date} {@time}
44 | %%%------------------------------------------------------------------------
45 |
46 | -module(btrie).
47 | -author('mjtruog at protonmail dot com').
48 |
49 | %% external interface
50 | -export([append/3,
51 | append_list/3,
52 | erase/2,
53 | erase_similar/2,
54 | fetch/2,
55 | fetch_keys/1,
56 | fetch_keys_similar/2,
57 | find_prefix/2,
58 | find_prefixes/2,
59 | find_prefix_longest/2,
60 | filter/2,
61 | find/2,
62 | fold/3,
63 | foldl/3,
64 | foldr/3,
65 | fold_similar/4,
66 | foldl_similar/4,
67 | foldr_similar/4,
68 | foreach/2,
69 | from_list/1,
70 | is_key/2,
71 | map/2,
72 | merge/3,
73 | new/0,
74 | new/1,
75 | prefix/3,
76 | size/1,
77 | store/2,
78 | store/3,
79 | take/2,
80 | to_list/1,
81 | to_list_similar/2,
82 | update/3,
83 | update/4,
84 | update_counter/3,
85 | test/0]).
86 |
87 | -define(MODE_BINARY, true).
88 | -include("trie.hrl").
89 |
90 | %%%------------------------------------------------------------------------
91 | %%% External interface functions
92 | %%%------------------------------------------------------------------------
93 |
94 | %%-------------------------------------------------------------------------
95 | %% @private
96 | %% @doc
97 | %% ===Regression test.===
98 | %% @end
99 | %%-------------------------------------------------------------------------
100 |
101 | test() ->
102 | {97,97,{{<<>>,empty}}} = btrie:new([<<"a">>]),
103 | {97,97,{{<<"b">>,empty}}} = btrie:new([<<"ab">>]),
104 | {97,97,{{<<"bc">>,empty}}} = btrie:new([<<"abc">>]),
105 | {97,97,{{<<"b">>,empty}}} = btrie:new([<<"ab">>]),
106 | {97,97,{{{97,98,{{<<>>,empty},{<<>>,empty}}},error}}} =
107 | btrie:new([<<"ab">>,<<"aa">>]),
108 | {97,97,{{{97,98,{{<<"c">>,empty},{<<"c">>,empty}}},error}}} =
109 | btrie:new([<<"abc">>,<<"aac">>]),
110 | {97,97,{{{97,98,{{<<"c">>,2},{<<"c">>,1}}},error}}} =
111 | btrie:new([{<<"abc">>, 1},{<<"aac">>, 2}]),
112 | {97,97,{{{97,98,{{<<"c">>,2},{<<"cdefghijklmnopqrstuvwxyz">>,1}}},error}}} =
113 | RootNode0 = btrie:new([
114 | {<<"abcdefghijklmnopqrstuvwxyz">>, 1},{<<"aac">>, 2}]),
115 | {ok, 1} = btrie:find(<<"abcdefghijklmnopqrstuvwxyz">>, RootNode0),
116 | error = btrie:find(<<"abcdefghijklmnopqrstuvwxy">>, RootNode0),
117 | {ok, 1} = btrie:find_prefix(<<"abcdefghijklmnopqrstuvwxyz">>, RootNode0),
118 | prefix = btrie:find_prefix(<<"abcdefghijklmnopqrstuvwxy">>, RootNode0),
119 | error = btrie:find_prefix(<<"abcdefghijklmnopqrstuvwxyzX">>, RootNode0),
120 | prefix = btrie:find_prefix(<<"a">>, RootNode0),
121 | prefix = btrie:find_prefix(<<"aa">>, RootNode0),
122 | {ok, 2} = btrie:find_prefix(<<"aac">>, RootNode0),
123 | error = btrie:find_prefix(<<"aacX">>, RootNode0),
124 | {97,97,{{{97,98,{{{98,99,{{<<"cde">>,3},{<<>>,2}}},error},
125 | {<<"cdefghijklmnopqrstuvwxyz">>,1}}},error}}} =
126 | RootNode1 = btrie:store(<<"aabcde">>, 3, RootNode0),
127 | {97,97,{{{97,98,{{{98,99,{{<<"cde">>,13},{<<>>,12}}},error},
128 | {<<"cdefghijklmnopqrstuvwxyz">>,11}}},error}}} =
129 | map(fun(_, V) -> V + 10 end, RootNode1),
130 | {97,97,{{{97,98,{{{98,99,{{<<>>,error},{<<>>,error}}},error},
131 | {<<"cdefghijklmnopqrstuvwxyz">>,1}}},error}}} =
132 | filter(fun(_, V) -> V =< 1 end, RootNode1),
133 | {97,97,{{{97,98,{{{98,99,{{<<>>,error},{<<>>,2}}},error},
134 | {<<"cdefghijklmnopqrstuvwxyz">>,1}}},error}}} =
135 | filter(fun(_, V) -> V =< 2 end, RootNode1),
136 | [<<"aabcde">>, <<"aac">>, <<"abcdefghijklmnopqrstuvwxyz">>] =
137 | btrie:fetch_keys(RootNode1),
138 | [{<<"aabcde">>, 3}, {<<"aac">>, 2},
139 | {<<"abcdefghijklmnopqrstuvwxyz">>, 1}] = btrie:to_list(RootNode1),
140 | [{<<"aabcde">>, 3}, {<<"aac">>, 12},
141 | {<<"abcdefghijklmnopqrstuvwxyz">>, 1}] =
142 | btrie:to_list(btrie:update(
143 | <<"aac">>, fun(I) -> I + 10 end, RootNode1)),
144 | [{<<"aaa">>, 4}, {<<"aabcde">>, 3}, {<<"aac">>, 2},
145 | {<<"abcdefghijklmnopqrstuvwxyz">>, 1}] =
146 | btrie:to_list(btrie:update(
147 | <<"aaa">>, fun(I) -> I + 10 end, 4, RootNode1)),
148 | 6 = foldl(fun(_, I, A) -> I + A end, 0, RootNode1),
149 | [{<<"aabcde">>, 3},{<<"aac">>, 2},{<<"abcdefghijklmnopqrstuvwxyz">>, 1}] =
150 | foldr(fun(K, V, A) -> [{K,V} | A] end, [], RootNode1),
151 | [{<<"abcdefghijklmnopqrstuvwxyz">>, 1}, {<<"aac">>, 2}, {<<"aabcde">>, 3}] =
152 | foldl(fun(K, V, A) -> [{K,V} | A] end, [], RootNode1),
153 | error = btrie:find(<<"aabcde">>, RootNode0),
154 | {ok, 3} = btrie:find(<<"aabcde">>, RootNode1),
155 | RootNode2 = btrie:erase(<<"aac">>, RootNode0),
156 | {ok, 1} = btrie:find(<<"abcdefghijklmnopqrstuvwxyz">>, RootNode2),
157 | {97,98,{{{98,98,{{<<>>,[2]}}},[1]},{<<"c">>,[3]}}} =
158 | RootNode3 = btrie:new([{<<"a">>, [1]},{<<"ab">>, [2]},{<<"bc">>, [3]}]),
159 | {97,98,{{{98,98,{{<<>>,[2]}}},[1,2]},{<<"c">>,[3]}}} =
160 | btrie:append(<<"a">>, 2, RootNode3),
161 |
162 | RootNode4 = btrie:new([
163 | {<<"ammmmmmm">>, 7},
164 | {<<"aaaaaaaaaaa">>, 4},
165 | {<<"aaa">>, 2},
166 | {<<"ab">>, 0},
167 | {<<"ab">>, 5},
168 | {<<"aa">>, 1},
169 | {<<"aba">>, 6},
170 | {<<"aaaaaaaa">>, 3}]),
171 | {97,97,
172 | {{{97,109,
173 | {{{97,97,
174 | {{{97,97,
175 | {{{97,97,
176 | {{{97,97,
177 | {{{97,97,
178 | {{{97,97,
179 | {{{97,97,
180 | {{<<"aa">>,4}}},
181 | 3}}},
182 | error}}},
183 | error}}},
184 | error}}},
185 | error}}},
186 | 2}}},
187 | 1},
188 | {{97,97,{{<<>>,6}}},5},
189 | {<<>>,error},
190 | {<<>>,error},
191 | {<<>>,error},
192 | {<<>>,error},
193 | {<<>>,error},
194 | {<<>>,error},
195 | {<<>>,error},
196 | {<<>>,error},
197 | {<<>>,error},
198 | {<<>>,error},
199 | {<<"mmmmmm">>,7}}},
200 | error}}} = RootNode4,
201 | [{<<"aa">>,1},
202 | {<<"aaa">>,2},
203 | {<<"aaaaaaaa">>,3},
204 | {<<"aaaaaaaaaaa">>,4},
205 | {<<"ab">>,5},
206 | {<<"aba">>,6},
207 | {<<"ammmmmmm">>,7}] = btrie:to_list(
208 | btrie:from_list(btrie:to_list(RootNode4))),
209 | [<<"aa">>,
210 | <<"aaa">>,
211 | <<"aaaaaaaa">>,
212 | <<"aaaaaaaaaaa">>,
213 | <<"ab">>,
214 | <<"aba">>,
215 | <<"ammmmmmm">>] = btrie:fetch_keys(RootNode4),
216 | [<<"aa">>,
217 | <<"aaa">>,
218 | <<"aaaaaaaa">>,
219 | <<"aaaaaaaaaaa">>,
220 | <<"ab">>,
221 | <<"aba">>,
222 | <<"ammmmmmm">>] = btrie:foldr(
223 | fun(Key, _, L) -> [Key | L] end, [], RootNode4),
224 | [<<"ammmmmmm">>,
225 | <<"aba">>,
226 | <<"ab">>,
227 | <<"aaaaaaaaaaa">>,
228 | <<"aaaaaaaa">>,
229 | <<"aaa">>,
230 | <<"aa">>] = btrie:foldl(
231 | fun(Key, _, L) -> [Key | L] end, [], RootNode4),
232 | RootNode5 = btrie:store(<<"a">>, 0,
233 | btrie:store(<<"aaaa">>, 2.5, RootNode4)),
234 | {ok, 2.5} = btrie:find(<<"aaaa">>, RootNode5),
235 | error = btrie:find(<<"aaaa">>, RootNode4),
236 | {ok, 2.5} = btrie:find_prefix(<<"aaaa">>, RootNode5),
237 | prefix = btrie:find_prefix(<<"aaaa">>, RootNode4),
238 | error = btrie:find_prefix_longest(<<"a">>, RootNode4),
239 | {ok, <<"aa">>, 1} = btrie:find_prefix_longest(<<"aa">>, RootNode4),
240 | {ok, <<"aaa">>, 2} = btrie:find_prefix_longest(<<"aaaa">>, RootNode4),
241 | {ok, <<"ab">>, 5} = btrie:find_prefix_longest(<<"absolut">>, RootNode4),
242 | {ok, <<"aba">>, 6} = btrie:find_prefix_longest(<<"aba">>, RootNode4),
243 | {ok, <<"aaaaaaaa">>, 3} = btrie:find_prefix_longest(<<"aaaaaaaaa">>, RootNode4),
244 | error = btrie:find_prefix_longest(<<"bar">>, RootNode4),
245 | {ok, <<"aaaaaaaaaaa">>, 4} = btrie:find_prefix_longest(<<"aaaaaaaaaaaaaaaaaaaaaddddddaa">>, RootNode4),
246 | 2.5 = btrie:fetch(<<"aaaa">>, RootNode5),
247 | {error, if_clause} = ?EXCEPTION(btrie:fetch(<<"aaaa">>, RootNode4)),
248 | RootNode4 = btrie:erase(<<"a">>, btrie:erase(<<"aaaa">>, RootNode5)),
249 | true = btrie:is_key(<<"aaaa">>, RootNode5),
250 | false = btrie:is_key(<<"aaaa">>, RootNode4),
251 | [<<"aa">>,
252 | <<"aaa">>,
253 | <<"aaaaaaaa">>,
254 | <<"aaaaaaaaaaa">>] = btrie:fetch_keys_similar(<<"aa">>, RootNode4),
255 | [<<"aaa">>,
256 | <<"aaaaaaaa">>,
257 | <<"aaaaaaaaaaa">>] = btrie:fetch_keys_similar(<<"aaac">>, RootNode4),
258 | [<<"ab">>,
259 | <<"aba">>] = btrie:fetch_keys_similar(<<"abba">>, RootNode4),
260 | [<<"aa">>,
261 | <<"aaa">>,
262 | <<"aaaaaaaa">>,
263 | <<"aaaaaaaaaaa">>,
264 | <<"ab">>,
265 | <<"aba">>,
266 | <<"ammmmmmm">>] = btrie:fetch_keys_similar(<<"a">>, RootNode4),
267 | [] = btrie:fetch_keys_similar(<<"b">>, RootNode4),
268 | _RootNode6 = btrie:new([
269 | {<<"*">>, 1},
270 | {<<"aa*">>, 2},
271 | {<<"aa*b">>, 3},
272 | {<<"aa*a*">>, 4},
273 | {<<"aaaaa">>, 5}]),
274 | ok.
275 |
276 | %%%------------------------------------------------------------------------
277 | %%% Private functions
278 | %%%------------------------------------------------------------------------
279 |
280 | %% make a new tuple with arity N and default D, then
281 | %% move tuple T into the new tuple at index I
282 | tuple_move(I, N, T, D)
283 | when is_integer(I), is_integer(N), is_tuple(T),
284 | (N - I + 1) >= tuple_size(T) ->
285 | tuple_move_i(I, 1, I + tuple_size(T), erlang:make_tuple(N, D), T).
286 |
287 | tuple_move_i(N1, _, N1, T1, _) ->
288 | T1;
289 |
290 | tuple_move_i(I1, I0, N1, T1, T0) ->
291 | tuple_move_i(I1 + 1, I0 + 1, N1,
292 | erlang:setelement(I1, T1, erlang:element(I0, T0)), T0).
293 |
294 | binary_prefix(<<>>, _) ->
295 | true;
296 | binary_prefix(KeyEnd1, KeyEnd2) ->
297 | case binary:match(KeyEnd2, KeyEnd1, []) of
298 | {0, _} ->
299 | true;
300 | _ ->
301 | false
302 | end.
303 |
304 | -ifdef(TEST).
305 | -include_lib("eunit/include/eunit.hrl").
306 |
307 | -include("trie_test.hrl").
308 |
309 | module_test_() ->
310 | {timeout, ?TEST_TIMEOUT, [
311 | {"internal tests", ?_assertOk(test())}
312 | ]}.
313 |
314 | -endif.
315 |
316 |
--------------------------------------------------------------------------------
/src/trie.app.src:
--------------------------------------------------------------------------------
1 | %-*-Mode:erlang;coding:utf-8;tab-width:4;c-basic-offset:4;indent-tabs-mode:()-*-
2 | % ex: set ft=erlang fenc=utf-8 sts=4 ts=4 sw=4 et nomod:
3 |
4 | {application, trie,
5 | [{description, "Trie Data Structure"},
6 | {vsn, "2.0.7"},
7 | {modules, [btrie, trie]},
8 | {registered, []},
9 | {applications, [stdlib, kernel]}]}.
10 |
11 |
--------------------------------------------------------------------------------
/src/trie.hrl:
--------------------------------------------------------------------------------
1 | %-*-Mode:erlang;coding:utf-8;tab-width:4;c-basic-offset:4;indent-tabs-mode:()-*-
2 | % ex: set ft=erlang fenc=utf-8 sts=4 ts=4 sw=4 et nomod:
3 | %%%
4 | %%%------------------------------------------------------------------------
5 | %%%
6 | %%% This file contains trie functions utilized by both the string
7 | %%% (list of integers) trie implementation and the binary trie
8 | %%% implementation.
9 | %%%
10 | %%% MIT License
11 | %%%
12 | %%% Copyright (c) 2010-2025 Michael Truog
13 | %%%
14 | %%% Permission is hereby granted, free of charge, to any person obtaining a
15 | %%% copy of this software and associated documentation files (the "Software"),
16 | %%% to deal in the Software without restriction, including without limitation
17 | %%% the rights to use, copy, modify, merge, publish, distribute, sublicense,
18 | %%% and/or sell copies of the Software, and to permit persons to whom the
19 | %%% Software is furnished to do so, subject to the following conditions:
20 | %%%
21 | %%% The above copyright notice and this permission notice shall be included in
22 | %%% all copies or substantial portions of the Software.
23 | %%%
24 | %%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25 | %%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26 | %%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
27 | %%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28 | %%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
29 | %%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
30 | %%% DEALINGS IN THE SOFTWARE.
31 | %%%
32 | %%%------------------------------------------------------------------------
33 |
34 | -ifdef(MODE_LIST).
35 | -define(TYPE_NAME, nonempty_string()).
36 | -define(TYPE_EMPTY, []).
37 | -define(TYPE_CHECK(V), is_list(V)).
38 | -define(TYPE_H0T0, [H | T]).
39 | -define(TYPE_H0_, [H | _]).
40 | -define(TYPE_H0, [H]).
41 | -define(TYPE_H1T1, [H1 | T1]).
42 | -define(TYPE_BHBT, [BH | BT]).
43 | -define(TYPE_KEYH0, Key ++ [H]).
44 | -define(TYPE_KEYH0T0, Key ++ [H] ++ T).
45 | -define(TYPE_KEYH0CHILDNODE, Key ++ [H] ++ ChildNode).
46 | -define(TYPE_KEYCHAR, Key ++ [Character]).
47 | -define(TYPE_KEYCHARNODE, Key ++ [Character] ++ Node).
48 | -define(TYPE_NEWKEYNODE, NewKey ++ Node).
49 | -define(TYPE_NEWKEY, [H | Key]).
50 | -define(TYPE_NEWKEY_REVERSE(X), lists:reverse(X)).
51 | -define(TYPE_NEWKEY_REVERSE(X, Y), lists:reverse(X, Y)).
52 | -define(TYPE_PREFIX(X, Y), lists:prefix(X, Y)).
53 | -else.
54 | -ifdef(MODE_BINARY).
55 | -define(TYPE_NAME, <<_:8, _:_*8>>).
56 | -define(TYPE_EMPTY, <<>>).
57 | -define(TYPE_CHECK(V), is_binary(V)).
58 | -define(TYPE_H0T0, <>).
59 | -define(TYPE_H0_, <>).
60 | -define(TYPE_H0, <>).
61 | -define(TYPE_H1T1, <>).
62 | -define(TYPE_BHBT, <>).
63 | -define(TYPE_KEYH0, <>).
64 | -define(TYPE_KEYH0T0, <>).
65 | -define(TYPE_KEYH0CHILDNODE, <>).
66 | -define(TYPE_KEYCHAR, <>).
67 | -define(TYPE_KEYCHARNODE, <>).
68 | -define(TYPE_NEWKEYNODE, <>).
69 | -define(TYPE_NEWKEY, <>).
70 | -define(TYPE_NEWKEY_REVERSE(X), X).
71 | -define(TYPE_NEWKEY_REVERSE(X, Y), <>).
72 | -define(TYPE_PREFIX(X, Y), binary_prefix(X, Y)).
73 | -endif.
74 | -endif.
75 |
76 | -define(EXCEPTION(E),
77 | (fun() ->
78 | try _ = E, no_exception
79 | catch ErrorType:Error -> {ErrorType, Error}
80 | end
81 | end)()).
82 |
83 | %%%------------------------------------------------------------------------
84 | %%% External interface functions
85 | %%%------------------------------------------------------------------------
86 |
87 | -type nonempty_trie() :: {integer(), integer(), tuple()}.
88 | -type empty_trie() :: ?TYPE_EMPTY.
89 | -type trie() :: nonempty_trie() | empty_trie().
90 | -export_type([nonempty_trie/0,
91 | empty_trie/0,
92 | trie/0]).
93 |
94 | %%-------------------------------------------------------------------------
95 | %% @doc
96 | %% ===Append a value as a list element in a trie instance.===
97 | %% @end
98 | %%-------------------------------------------------------------------------
99 |
100 | -spec append(Key :: ?TYPE_NAME,
101 | Value :: any(),
102 | Node :: trie()) -> nonempty_trie().
103 |
104 | append(Key, Value, Node) ->
105 | ValueList = [Value],
106 | update(Key, fun(OldValue) -> OldValue ++ ValueList end, ValueList, Node).
107 |
108 | %%-------------------------------------------------------------------------
109 | %% @doc
110 | %% ===Append a list of values as a list element in a trie instance.===
111 | %% @end
112 | %%-------------------------------------------------------------------------
113 |
114 | -spec append_list(Key :: ?TYPE_NAME,
115 | ValueList :: list(),
116 | Node :: trie()) -> nonempty_trie().
117 |
118 | append_list(Key, ValueList, Node) ->
119 | update(Key, fun(OldValue) -> OldValue ++ ValueList end, ValueList, Node).
120 |
121 | %%-------------------------------------------------------------------------
122 | %% @doc
123 | %% ===Erase a value in a trie.===
124 | %% @end
125 | %%-------------------------------------------------------------------------
126 |
127 | -spec erase(Key :: ?TYPE_NAME,
128 | Node :: trie()) -> trie().
129 |
130 | erase(_, ?TYPE_EMPTY = Node) ->
131 | Node;
132 |
133 | erase(?TYPE_H0T0, Node) ->
134 | erase_node(H, T, Node).
135 |
136 | erase_node(H, _, {I0, I1, _} = Node)
137 | when is_integer(H), H < I0;
138 | is_integer(H), H > I1 ->
139 | Node;
140 |
141 | erase_node(H, T, {I0, I1, Data} = OldNode)
142 | when is_integer(H) ->
143 | I = H - I0 + 1,
144 | {Node, Value} = erlang:element(I, Data),
145 | if
146 | T == Node ->
147 | if
148 | Value =:= error ->
149 | OldNode;
150 | true ->
151 | {I0, I1, erlang:setelement(I, Data, {?TYPE_EMPTY, error})}
152 | end;
153 | T =:= ?TYPE_EMPTY ->
154 | if
155 | Value =:= error ->
156 | OldNode;
157 | true ->
158 | {I0, I1, erlang:setelement(I, Data, {Node, error})}
159 | end;
160 | ?TYPE_CHECK(Node) ->
161 | OldNode;
162 | is_tuple(Node) ->
163 | ?TYPE_H1T1 = T,
164 | {I0, I1, erlang:setelement(I, Data,
165 | {erase_node(H1, T1, Node), Value})}
166 | end.
167 |
168 | %%-------------------------------------------------------------------------
169 | %% @doc
170 | %% ===Erase all entries within a trie that share a common prefix.===
171 | %% @end
172 | %%-------------------------------------------------------------------------
173 |
174 | -spec erase_similar(Similar :: ?TYPE_NAME,
175 | Node :: trie()) -> list(?TYPE_NAME).
176 |
177 | erase_similar(Similar, Node) ->
178 | fold_similar(Similar, fun(Key, _, N) -> erase(Key, N) end, Node, Node).
179 |
180 | %%-------------------------------------------------------------------------
181 | %% @doc
182 | %% ===Fetch a value from a trie.===
183 | %% @end
184 | %%-------------------------------------------------------------------------
185 |
186 | -spec fetch(?TYPE_NAME,
187 | nonempty_trie()) -> any().
188 |
189 | fetch(?TYPE_H0T0, {_, _, _} = Node) ->
190 | fetch_node(H, T, Node).
191 |
192 | fetch_node(H, T, {I0, I1, Data})
193 | when is_integer(H), H >= I0, H =< I1 ->
194 | {Node, Value} = erlang:element(H - I0 + 1, Data),
195 | case T of
196 | ?TYPE_EMPTY ->
197 | if
198 | is_tuple(Node); Node =:= ?TYPE_EMPTY ->
199 | if
200 | Value =/= error ->
201 | Value
202 | end
203 | end;
204 | ?TYPE_H1T1 ->
205 | case Node of
206 | {_, _, _} ->
207 | fetch_node(H1, T1, Node);
208 | T when Value =/= error ->
209 | Value
210 | end
211 | end.
212 |
213 | %%-------------------------------------------------------------------------
214 | %% @doc
215 | %% ===Fetch all the keys in a trie.===
216 | %% @end
217 | %%-------------------------------------------------------------------------
218 |
219 | -spec fetch_keys(Node :: trie()) -> list(?TYPE_NAME).
220 |
221 | fetch_keys(Node) ->
222 | foldr(fun(Key, _, L) -> [Key | L] end, [], Node).
223 |
224 | %%-------------------------------------------------------------------------
225 | %% @doc
226 | %% ===Fetch the keys within a trie that share a common prefix.===
227 | %% @end
228 | %%-------------------------------------------------------------------------
229 |
230 | -spec fetch_keys_similar(Similar :: ?TYPE_NAME,
231 | Node :: trie()) -> list(?TYPE_NAME).
232 |
233 | fetch_keys_similar(Similar, Node) ->
234 | foldr_similar(Similar, fun(Key, _, L) -> [Key | L] end, [], Node).
235 |
236 | %%-------------------------------------------------------------------------
237 | %% @doc
238 | %% ===Filter a trie with a predicate function.===
239 | %% @end
240 | %%-------------------------------------------------------------------------
241 |
242 | -spec filter(F :: fun((?TYPE_NAME, any()) -> boolean()),
243 | Node :: trie()) -> trie().
244 |
245 | filter(F, ?TYPE_EMPTY = Node) when is_function(F, 2) ->
246 | Node;
247 |
248 | filter(F, Node) when is_function(F, 2) ->
249 | filter_node(F, ?TYPE_EMPTY, Node).
250 |
251 | filter_node(F, Key, {I0, I1, Data}) ->
252 | {I0, I1, filter_element(F, I1 - I0 + 1, I0 - 1, Key, Data)};
253 |
254 | filter_node(_, _, Node)
255 | when ?TYPE_CHECK(Node) ->
256 | Node.
257 |
258 | filter_element(_, 0, _, _, Data) ->
259 | Data;
260 |
261 | filter_element(F, I, Offset, Key, Data) ->
262 | {Node, Value} = erlang:element(I, Data),
263 | Character = Offset + I,
264 | if
265 | Node =:= ?TYPE_EMPTY ->
266 | if
267 | Value =:= error ->
268 | filter_element(F, I - 1, Offset, Key, Data);
269 | true ->
270 | case F(?TYPE_KEYCHAR, Value) of
271 | true ->
272 | filter_element(F, I - 1, Offset, Key, Data);
273 | false ->
274 | filter_element(F, I - 1, Offset, Key,
275 | erlang:setelement(I, Data,
276 | {?TYPE_EMPTY, error}))
277 | end
278 | end;
279 | Value =:= error ->
280 | filter_element(F, I - 1, Offset, Key, erlang:setelement(I, Data,
281 | {filter_node(F, ?TYPE_KEYCHAR, Node), Value}));
282 | true ->
283 | NewKey = ?TYPE_KEYCHAR,
284 | if
285 | ?TYPE_CHECK(Node) ->
286 | case F(?TYPE_NEWKEYNODE, Value) of
287 | true ->
288 | filter_element(F, I - 1, Offset, Key, Data);
289 | false ->
290 | filter_element(F, I - 1, Offset, Key,
291 | erlang:setelement(I, Data,
292 | {?TYPE_EMPTY, error}))
293 | end;
294 | true ->
295 | case F(NewKey, Value) of
296 | true ->
297 | filter_element(F, I - 1, Offset, Key, Data);
298 | false ->
299 | filter_element(F, I - 1, Offset, Key,
300 | erlang:setelement(I, Data,
301 | {filter_node(F, NewKey, Node), error}))
302 | end
303 | end
304 | end.
305 |
306 | %%-------------------------------------------------------------------------
307 | %% @doc
308 | %% ===Find a value in a trie.===
309 | %% @end
310 | %%-------------------------------------------------------------------------
311 |
312 | -spec find(?TYPE_NAME, trie()) -> {ok, any()} | 'error'.
313 |
314 | find(_, ?TYPE_EMPTY) ->
315 | error;
316 |
317 | find(?TYPE_H0T0, {_, _, _} = Node) ->
318 | find_node(H, T, Node).
319 |
320 | find_node(H, _, {I0, I1, _})
321 | when is_integer(H), H < I0;
322 | is_integer(H), H > I1 ->
323 | error;
324 |
325 | find_node(H, ?TYPE_EMPTY, {I0, _, Data})
326 | when is_integer(H) ->
327 | {Node, Value} = erlang:element(H - I0 + 1, Data),
328 | if
329 | is_tuple(Node); Node =:= ?TYPE_EMPTY ->
330 | if
331 | Value =:= error ->
332 | error;
333 | true ->
334 | {ok, Value}
335 | end;
336 | true ->
337 | error
338 | end;
339 |
340 | find_node(H, T, {I0, _, Data})
341 | when is_integer(H) ->
342 | {Node, Value} = erlang:element(H - I0 + 1, Data),
343 | case Node of
344 | {_, _, _} ->
345 | ?TYPE_H1T1 = T,
346 | find_node(H1, T1, Node);
347 | T ->
348 | if
349 | Value =:= error ->
350 | error;
351 | true ->
352 | {ok, Value}
353 | end;
354 | _ ->
355 | error
356 | end.
357 |
358 | %%-------------------------------------------------------------------------
359 | %% @doc
360 | %% ===Find a value in a trie by prefix.===
361 | %% The atom 'prefix' is returned if the string supplied is a prefix
362 | %% for a key that has previously been stored within the trie, but no
363 | %% value was found, since there was no exact match for the string supplied.
364 | %% @end
365 | %%-------------------------------------------------------------------------
366 |
367 | -spec find_prefix(?TYPE_NAME, trie()) -> {ok, any()} | 'prefix' | 'error'.
368 |
369 | find_prefix(?TYPE_H0_, {I0, I1, _})
370 | when H < I0; H > I1 ->
371 | error;
372 |
373 | find_prefix(?TYPE_H0, {I0, _, Data})
374 | when is_integer(H) ->
375 | case erlang:element(H - I0 + 1, Data) of
376 | {{_, _, _}, error} ->
377 | prefix;
378 | {{_, _, _}, Value} ->
379 | {ok, Value};
380 | {_, error} ->
381 | error;
382 | {?TYPE_EMPTY, Value} ->
383 | {ok, Value};
384 | {_, _} ->
385 | prefix
386 | end;
387 |
388 | find_prefix(?TYPE_H0T0, {I0, _, Data})
389 | when is_integer(H) ->
390 | case erlang:element(H - I0 + 1, Data) of
391 | {{_, _, _} = Node, _} ->
392 | find_prefix(T, Node);
393 | {_, error} ->
394 | error;
395 | {T, Value} ->
396 | {ok, Value};
397 | {L, _} ->
398 | case ?TYPE_PREFIX(T, L) of
399 | true ->
400 | prefix;
401 | false ->
402 | error
403 | end
404 | end;
405 |
406 | find_prefix(_, ?TYPE_EMPTY) ->
407 | error.
408 |
409 | %%-------------------------------------------------------------------------
410 | %% @doc
411 | %% ===Find the longest key in a trie that is a prefix to the passed string.===
412 | %% @end
413 | %%-------------------------------------------------------------------------
414 |
415 | -spec find_prefix_longest(Match :: ?TYPE_NAME,
416 | Node :: trie()) ->
417 | {ok, ?TYPE_NAME, any()} | 'error'.
418 |
419 | find_prefix_longest(Match, Node) when is_tuple(Node) ->
420 | find_prefix_longest(Match, ?TYPE_EMPTY, error, Node);
421 |
422 | find_prefix_longest(_Match, ?TYPE_EMPTY) ->
423 | error.
424 |
425 | find_prefix_longest(?TYPE_H0T0, Key, LastMatch, {I0, I1, Data})
426 | when is_integer(H), H >= I0, H =< I1 ->
427 | {ChildNode, Value} = erlang:element(H - I0 + 1, Data),
428 | if
429 | is_tuple(ChildNode) ->
430 | %% If the prefix matched and there are other child leaf nodes
431 | %% for this prefix, then update the last match to the current
432 | %% prefix and continue recursing over the trie.
433 | NewKey = ?TYPE_NEWKEY,
434 | NewMatch = if
435 | Value =:= error ->
436 | LastMatch;
437 | true ->
438 | {NewKey, Value}
439 | end,
440 | find_prefix_longest(T, NewKey, NewMatch, ChildNode);
441 | true ->
442 | %% If this is a leaf node and the key for the current node is a
443 | %% prefix for the passed value, then return a match on the current
444 | %% node. Otherwise, return the last match we had found previously.
445 | case ?TYPE_PREFIX(ChildNode, T) of
446 | true when Value =/= error ->
447 | {ok, ?TYPE_NEWKEY_REVERSE(?TYPE_NEWKEY, ChildNode), Value};
448 | _ ->
449 | case LastMatch of
450 | {LastKey, LastValue} ->
451 | {ok, ?TYPE_NEWKEY_REVERSE(LastKey), LastValue};
452 | error ->
453 | error
454 | end
455 | end
456 | end;
457 |
458 | find_prefix_longest(_Match, _Key, {LastKey, LastValue}, _Node) ->
459 | {ok, ?TYPE_NEWKEY_REVERSE(LastKey), LastValue};
460 |
461 | find_prefix_longest(_Match, _Key, error, _Node) ->
462 | error.
463 |
464 | %%-------------------------------------------------------------------------
465 | %% @doc
466 | %% ===Find all the keys in a trie that are prefixes to the passed string.===
467 | %% The entries are returned in alphabetical order.
468 | %% @end
469 | %%-------------------------------------------------------------------------
470 |
471 | -spec find_prefixes(Match :: ?TYPE_NAME,
472 | Node :: trie()) ->
473 | list({?TYPE_NAME, any()}).
474 |
475 | find_prefixes(Match, Node) when is_tuple(Node) ->
476 | find_prefixes(Match, ?TYPE_EMPTY, [], Node);
477 |
478 | find_prefixes(_Match, ?TYPE_EMPTY) ->
479 | [].
480 |
481 | find_prefixes(?TYPE_H0T0, Key, Acc, {I0, I1, Data})
482 | when is_integer(H), H >= I0, H =< I1 ->
483 | {ChildNode, Value} = erlang:element(H - I0 + 1, Data),
484 | if
485 | is_tuple(ChildNode) ->
486 | %% If the prefix matched and there are other child leaf nodes
487 | %% for this prefix, then add the match to the current list
488 | %% and continue recursing over the trie.
489 | NewKey = ?TYPE_NEWKEY,
490 | NewAcc = if
491 | Value =:= error ->
492 | Acc;
493 | true ->
494 | [{?TYPE_NEWKEY_REVERSE(NewKey), Value} | Acc]
495 | end,
496 | find_prefixes(T, NewKey, NewAcc, ChildNode);
497 | true ->
498 | %% If this is a leaf node and the key for the current node is a
499 | %% prefix for the passed value, then add a match on the current
500 | %% node. Otherwise, return the last match we had found previously.
501 | NewAcc = case ?TYPE_PREFIX(ChildNode, T) of
502 | true when Value =/= error ->
503 | [{?TYPE_NEWKEY_REVERSE(?TYPE_NEWKEY, ChildNode), Value} |
504 | Acc];
505 | _ ->
506 | Acc
507 | end,
508 | lists:reverse(NewAcc)
509 | end;
510 |
511 | find_prefixes(_Match, _Key, Acc, _Node) ->
512 | lists:reverse(Acc).
513 |
514 | %%-------------------------------------------------------------------------
515 | %% @doc
516 | %% ===Fold a function over the trie.===
517 | %% Traverses in alphabetical order.
518 | %% @end
519 | %%-------------------------------------------------------------------------
520 |
521 | -spec fold(F :: fun((?TYPE_NAME, any(), any()) -> any()),
522 | A :: any(),
523 | Node :: trie()) -> any().
524 |
525 | fold(F, A, Node) when is_function(F, 3) ->
526 | foldl(F, A, Node).
527 |
528 | %%-------------------------------------------------------------------------
529 | %% @doc
530 | %% ===Fold a function over the trie.===
531 | %% Traverses in alphabetical order.
532 | %% @end
533 | %%-------------------------------------------------------------------------
534 |
535 | -spec foldl(F :: fun((?TYPE_NAME, any(), any()) -> any()),
536 | A :: any(),
537 | Node :: trie()) -> any().
538 |
539 | foldl(F, A, ?TYPE_EMPTY) when is_function(F, 3) ->
540 | A;
541 |
542 | foldl(F, A, Node) when is_function(F, 3) ->
543 | foldl(F, A, ?TYPE_EMPTY, Node).
544 |
545 | foldl(F, A, Key, {I0, I1, Data}) ->
546 | foldl_element(F, A, 1, I1 - I0 + 2, I0 - 1, Key, Data).
547 |
548 | foldl_element(_, A, N, N, _, _, _) ->
549 | A;
550 |
551 | foldl_element(F, A, I, N, Offset, Key, Data) ->
552 | {Node, Value} = erlang:element(I, Data),
553 | Character = Offset + I,
554 | if
555 | ?TYPE_CHECK(Node) =:= false ->
556 | if
557 | Value =:= error ->
558 | foldl_element(F, foldl(F, A, ?TYPE_KEYCHAR, Node),
559 | I + 1, N, Offset, Key, Data);
560 | true ->
561 | NewKey = ?TYPE_KEYCHAR,
562 | foldl_element(F,
563 | foldl(F, F(NewKey, Value, A), NewKey, Node),
564 | I + 1, N, Offset, Key, Data)
565 | end;
566 | true ->
567 | if
568 | Value =:= error ->
569 | foldl_element(F, A,
570 | I + 1, N, Offset, Key, Data);
571 | true ->
572 | foldl_element(F, F(?TYPE_KEYCHARNODE, Value, A),
573 | I + 1, N, Offset, Key, Data)
574 | end
575 | end.
576 |
577 | %%-------------------------------------------------------------------------
578 | %% @doc
579 | %% ===Fold a function over the trie in reverse.===
580 | %% Traverses in reverse alphabetical order.
581 | %% @end
582 | %%-------------------------------------------------------------------------
583 |
584 | -spec foldr(F :: fun((?TYPE_NAME, any(), any()) -> any()),
585 | A :: any(),
586 | Node :: trie()) -> any().
587 |
588 | foldr(F, A, ?TYPE_EMPTY) when is_function(F, 3) ->
589 | A;
590 |
591 | foldr(F, A, Node) when is_function(F, 3) ->
592 | foldr(F, A, ?TYPE_EMPTY, Node).
593 |
594 | foldr(F, A, Key, {I0, I1, Data}) ->
595 | foldr_element(F, A, I1 - I0 + 1, I0 - 1, Key, Data).
596 |
597 | foldr_element(_, A, 0, _, _, _) ->
598 | A;
599 |
600 | foldr_element(F, A, I, Offset, Key, Data) ->
601 | {Node, Value} = erlang:element(I, Data),
602 | Character = Offset + I,
603 | if
604 | ?TYPE_CHECK(Node) =:= false ->
605 | if
606 | Value =:= error ->
607 | foldr_element(F, foldr(F, A, ?TYPE_KEYCHAR, Node),
608 | I - 1, Offset, Key, Data);
609 | true ->
610 | NewKey = ?TYPE_KEYCHAR,
611 | foldr_element(F,
612 | F(NewKey, Value, foldr(F, A, NewKey, Node)),
613 | I - 1, Offset, Key, Data)
614 | end;
615 | true ->
616 | if
617 | Value =:= error ->
618 | foldr_element(F, A,
619 | I - 1, Offset, Key, Data);
620 | true ->
621 | foldr_element(F, F(?TYPE_KEYCHARNODE, Value, A),
622 | I - 1, Offset, Key, Data)
623 | end
624 | end.
625 |
626 | %%-------------------------------------------------------------------------
627 | %% @doc
628 | %% ===Fold a function over the keys within a trie that share a common prefix.===
629 | %% Traverses in alphabetical order.
630 | %% @end
631 | %%-------------------------------------------------------------------------
632 |
633 | -spec fold_similar(Similar :: ?TYPE_NAME,
634 | F :: fun((?TYPE_NAME, any(), any()) -> any()),
635 | A :: any(),
636 | Node :: trie()) -> any().
637 |
638 | fold_similar(Similar, F, A, Node) ->
639 | foldl_similar(Similar, F, A, Node).
640 |
641 | %%-------------------------------------------------------------------------
642 | %% @doc
643 | %% ===Fold a function over the keys within a trie that share a common prefix.===
644 | %% Traverses in alphabetical order.
645 | %% @end
646 | %%-------------------------------------------------------------------------
647 |
648 | -spec foldl_similar(Similar :: ?TYPE_NAME,
649 | F :: fun((?TYPE_NAME, any(), any()) -> any()),
650 | A :: any(),
651 | Node :: trie()) -> any().
652 |
653 | foldl_similar(?TYPE_H0_, _, A, {I0, I1, _})
654 | when is_integer(H), H < I0;
655 | is_integer(H), H > I1 ->
656 | A;
657 |
658 | foldl_similar(_, _, A, ?TYPE_EMPTY) ->
659 | A;
660 |
661 | foldl_similar(?TYPE_H0T0, F, A, Node) ->
662 | fold_similar_node(H, T, foldl, F, A, ?TYPE_EMPTY, error, Node).
663 |
664 | %%-------------------------------------------------------------------------
665 | %% @doc
666 | %% ===Fold a function over the keys within a trie that share a common prefix in reverse.===
667 | %% Traverses in reverse alphabetical order.
668 | %% @end
669 | %%-------------------------------------------------------------------------
670 |
671 | -spec foldr_similar(Similar :: ?TYPE_NAME,
672 | F :: fun((?TYPE_NAME, any(), any()) -> any()),
673 | A :: any(),
674 | Node :: trie()) -> any().
675 |
676 | foldr_similar(?TYPE_H0_, _, A, {I0, I1, _})
677 | when is_integer(H), H < I0;
678 | is_integer(H), H > I1 ->
679 | A;
680 |
681 | foldr_similar(_, _, A, ?TYPE_EMPTY) ->
682 | A;
683 |
684 | foldr_similar(?TYPE_H0T0, F, A, Node) ->
685 | fold_similar_node(H, T, foldr, F, A, ?TYPE_EMPTY, error, Node).
686 |
687 | fold_similar_node(H, _, Fold, F, A, Key, LastValue, {I0, I1, _} = Node)
688 | when is_integer(H), H < I0;
689 | is_integer(H), H > I1 ->
690 | if
691 | LastValue =:= error ->
692 | fold_similar_element(Fold, F, A, Key, Node);
693 | Fold =:= foldl ->
694 | fold_similar_element(Fold, F, F(Key, LastValue, A), Key, Node);
695 | Fold =:= foldr ->
696 | F(Key, LastValue, fold_similar_element(Fold, F, A, Key, Node))
697 | end;
698 |
699 | fold_similar_node(H, ?TYPE_EMPTY, Fold, F, A, Key, _, {I0, _, Data} = Node)
700 | when is_integer(H) ->
701 | {ChildNode, Value} = erlang:element(H - I0 + 1, Data),
702 | if
703 | is_tuple(ChildNode) ->
704 | NewKey = ?TYPE_KEYH0,
705 | if
706 | Value =:= error ->
707 | fold_similar_element(Fold, F, A, NewKey, ChildNode);
708 | Fold =:= foldl ->
709 | fold_similar_element(Fold, F, F(NewKey, Value, A),
710 | NewKey, ChildNode);
711 | Fold =:= foldr ->
712 | F(NewKey, Value,
713 | fold_similar_element(Fold, F, A, NewKey, ChildNode))
714 | end;
715 | Value =/= error, ?TYPE_CHECK(ChildNode) ->
716 | F(?TYPE_KEYH0CHILDNODE, Value, A);
717 | true ->
718 | fold_similar_element(Fold, F, A, Key, Node)
719 | end;
720 |
721 | fold_similar_node(H, T, Fold, F, A, Key, _, {I0, _, Data} = Node)
722 | when is_integer(H) ->
723 | {ChildNode, Value} = erlang:element(H - I0 + 1, Data),
724 | if
725 | is_tuple(ChildNode) ->
726 | ?TYPE_H1T1 = T,
727 | fold_similar_node(H1, T1, Fold, F, A,
728 | ?TYPE_KEYH0, Value, ChildNode);
729 | Value =/= error, ?TYPE_CHECK(ChildNode) ->
730 | F(?TYPE_KEYH0CHILDNODE, Value, A);
731 | true ->
732 | fold_similar_element(Fold, F, A, Key, Node)
733 | end.
734 |
735 | fold_similar_element(foldl, F, A, Key, Node) ->
736 | foldl(F, A, Key, Node);
737 |
738 | fold_similar_element(foldr, F, A, Key, Node) ->
739 | foldr(F, A, Key, Node).
740 |
741 | %%-------------------------------------------------------------------------
742 | %% @doc
743 | %% ===Call a function for each element.===
744 | %% Traverses in alphabetical order.
745 | %% @end
746 | %%-------------------------------------------------------------------------
747 |
748 | -spec foreach(F :: fun((?TYPE_NAME, any()) -> any()),
749 | Node :: trie()) -> any().
750 |
751 | foreach(F, ?TYPE_EMPTY) when is_function(F, 2) ->
752 | ok;
753 |
754 | foreach(F, Node) when is_function(F, 2) ->
755 | foreach(F, ?TYPE_EMPTY, Node).
756 |
757 | foreach(F, Key, {I0, I1, Data}) ->
758 | foreach_element(F, 1, I1 - I0 + 2, I0 - 1, Key, Data).
759 |
760 | foreach_element(_, N, N, _, _, _) ->
761 | ok;
762 |
763 | foreach_element(F, I, N, Offset, Key, Data) ->
764 | {Node, Value} = erlang:element(I, Data),
765 | Character = Offset + I,
766 | if
767 | ?TYPE_CHECK(Node) =:= false ->
768 | if
769 | Value =:= error ->
770 | foreach(F, ?TYPE_KEYCHAR, Node),
771 | foreach_element(F, I + 1, N, Offset, Key, Data);
772 | true ->
773 | NewKey = ?TYPE_KEYCHAR,
774 | F(NewKey, Value),
775 | foreach(F, NewKey, Node),
776 | foreach_element(F, I + 1, N, Offset, Key, Data)
777 | end;
778 | true ->
779 | if
780 | Value =:= error ->
781 | foreach_element(F, I + 1, N, Offset, Key, Data);
782 | true ->
783 | F(?TYPE_KEYCHARNODE, Value),
784 | foreach_element(F, I + 1, N, Offset, Key, Data)
785 | end
786 | end.
787 |
788 | %%-------------------------------------------------------------------------
789 | %% @doc
790 | %% ===Create a trie from a list.===
791 | %% @end
792 | %%-------------------------------------------------------------------------
793 |
794 | -spec from_list(list(?TYPE_NAME | tuple())) -> trie().
795 |
796 | from_list(L) ->
797 | new(L).
798 |
799 | %%-------------------------------------------------------------------------
800 | %% @doc
801 | %% ===Determine if a key exists in a trie.===
802 | %% @end
803 | %%-------------------------------------------------------------------------
804 |
805 | -spec is_key(?TYPE_NAME, trie()) -> boolean().
806 |
807 | is_key(_, ?TYPE_EMPTY) ->
808 | false;
809 |
810 | is_key(?TYPE_H0T0, {_, _, _} = Node) ->
811 | is_key_node(H, T, Node).
812 |
813 | is_key_node(H, _, {I0, I1, _})
814 | when is_integer(H), H < I0;
815 | is_integer(H), H > I1 ->
816 | false;
817 |
818 | is_key_node(H, ?TYPE_EMPTY, {I0, _, Data})
819 | when is_integer(H) ->
820 | {Node, Value} = erlang:element(H - I0 + 1, Data),
821 | if
822 | is_tuple(Node); Node =:= ?TYPE_EMPTY ->
823 | (Value =/= error);
824 | true ->
825 | false
826 | end;
827 |
828 | is_key_node(H, T, {I0, _, Data})
829 | when is_integer(H) ->
830 | {Node, Value} = erlang:element(H - I0 + 1, Data),
831 | case Node of
832 | {_, _, _} ->
833 | ?TYPE_H1T1 = T,
834 | is_key_node(H1, T1, Node);
835 | T ->
836 | (Value =/= error);
837 | _ ->
838 | false
839 | end.
840 |
841 | %%-------------------------------------------------------------------------
842 | %% @doc
843 | %% ===Map a function over a trie.===
844 | %% Traverses in reverse alphabetical order.
845 | %% @end
846 | %%-------------------------------------------------------------------------
847 |
848 | -spec map(F :: fun((?TYPE_NAME, any()) -> any()),
849 | Node :: trie()) -> trie().
850 |
851 | map(F, ?TYPE_EMPTY = Node) when is_function(F, 2) ->
852 | Node;
853 |
854 | map(F, Node) when is_function(F, 2) ->
855 | map_node(F, ?TYPE_EMPTY, Node).
856 |
857 | map_node(F, Key, {I0, I1, Data}) ->
858 | {I0, I1, map_element(F, I1 - I0 + 1, I0 - 1, Key, Data)};
859 |
860 | map_node(_, _, Node)
861 | when ?TYPE_CHECK(Node) ->
862 | Node.
863 |
864 | map_element(_, 0, _, _, Data) ->
865 | Data;
866 |
867 | map_element(F, I, Offset, Key, Data) ->
868 | {Node, Value} = erlang:element(I, Data),
869 | Character = Offset + I,
870 | NewKey = ?TYPE_KEYCHAR,
871 | if
872 | Node =:= ?TYPE_EMPTY ->
873 | if
874 | Value =:= error ->
875 | map_element(F, I - 1, Offset, Key, Data);
876 | true ->
877 | map_element(F, I - 1, Offset, Key,
878 | erlang:setelement(I, Data, {Node, F(NewKey, Value)}))
879 | end;
880 | Value =:= error ->
881 | map_element(F, I - 1, Offset, Key, erlang:setelement(I, Data,
882 | {map_node(F, NewKey, Node), Value}));
883 | ?TYPE_CHECK(Node) ->
884 | map_element(F, I - 1, Offset, Key, erlang:setelement(I, Data,
885 | {map_node(F, NewKey, Node), F(?TYPE_NEWKEYNODE, Value)}));
886 | true ->
887 | map_element(F, I - 1, Offset, Key, erlang:setelement(I, Data,
888 | {map_node(F, NewKey, Node), F(NewKey, Value)}))
889 | end.
890 |
891 | %%-------------------------------------------------------------------------
892 | %% @doc
893 | %% ===Merge two trie instance.===
894 | %% Update the second trie parameter with all of the elements
895 | %% found within the first trie parameter.
896 | %% @end
897 | %%-------------------------------------------------------------------------
898 |
899 | -spec merge(F :: fun((?TYPE_NAME, any(), any()) -> any()),
900 | Node1 :: trie(),
901 | Node2 :: trie()) -> trie().
902 |
903 | merge(F, Node1, ?TYPE_EMPTY) when is_function(F, 3) ->
904 | Node1;
905 |
906 | merge(F, ?TYPE_EMPTY, Node2) when is_function(F, 3) ->
907 | Node2;
908 |
909 | merge(F, Node1, Node2) when is_function(F, 3) ->
910 | fold(fun (Key, V1, Node) ->
911 | update(Key, fun (V2) -> F(Key, V1, V2) end, V1, Node)
912 | end, Node2, Node1).
913 |
914 | %%-------------------------------------------------------------------------
915 | %% @doc
916 | %% ===Create a new trie instance.===
917 | %% @end
918 | %%-------------------------------------------------------------------------
919 |
920 | -spec new() -> empty_trie().
921 |
922 | new() ->
923 | ?TYPE_EMPTY.
924 |
925 | %%-------------------------------------------------------------------------
926 | %% @doc
927 | %% ===Create a new trie instance from a list.===
928 | %% The list may contain either: strings, 2 element tuples with a string as the
929 | %% first tuple element, or tuples with more than 2 elements (including records)
930 | %% with a string as the first element (second element if it is a record).
931 | %% If a list of records (or tuples larger than 2 elements) is provided,
932 | %% the whole record/tuple is stored as the value.
933 | %% @end
934 | %%-------------------------------------------------------------------------
935 |
936 | -spec new(L :: list(?TYPE_NAME | tuple())) -> trie().
937 |
938 | new(L) ->
939 | new_instance(L, new()).
940 |
941 | new_instance([], Node) ->
942 | Node;
943 |
944 | new_instance([{Key, Value} | T], Node) ->
945 | new_instance(T, store(Key, Value, Node));
946 |
947 | new_instance([Tuple | T], Node)
948 | when is_tuple(Tuple) ->
949 | FirstElement = erlang:element(1, Tuple),
950 | Key = if
951 | is_atom(FirstElement) ->
952 | erlang:element(2, Tuple);
953 | true ->
954 | FirstElement
955 | end,
956 | new_instance(T, store(Key, Tuple, Node));
957 |
958 | new_instance([Key | T], Node) ->
959 | new_instance(T, store(Key, Node)).
960 |
961 | new_instance_state(?TYPE_H0T0, V1, V0)
962 | when is_integer(H) ->
963 | {{H, H, {{T, V1}}}, V0}.
964 |
965 | %%-------------------------------------------------------------------------
966 | %% @doc
967 | %% ===Insert a value as the first list element in a trie instance.===
968 | %% The reverse of append/3.
969 | %% @end
970 | %%-------------------------------------------------------------------------
971 |
972 | -spec prefix(Key :: ?TYPE_NAME,
973 | Value :: any(),
974 | Node :: trie()) -> nonempty_trie().
975 |
976 | prefix(Key, Value, Node) ->
977 | update(Key, fun(OldValue) -> [Value | OldValue] end, [Value], Node).
978 |
979 | %%-------------------------------------------------------------------------
980 | %% @doc
981 | %% ===Size of a trie instance.===
982 | %% @end
983 | %%-------------------------------------------------------------------------
984 |
985 | -spec size(Node :: trie()) -> non_neg_integer().
986 |
987 | size(Node) ->
988 | fold(fun(_, _, I) -> I + 1 end, 0, Node).
989 |
990 | %%-------------------------------------------------------------------------
991 | %% @doc
992 | %% ===Store only a key in a trie instance.===
993 | %% @end
994 | %%-------------------------------------------------------------------------
995 |
996 | -spec store(Key :: ?TYPE_NAME,
997 | Node :: trie()) -> nonempty_trie().
998 |
999 | store(Key, Node) ->
1000 | store(Key, empty, Node).
1001 |
1002 | %%-------------------------------------------------------------------------
1003 | %% @doc
1004 | %% ===Store a key/value pair in a trie instance.===
1005 | %% @end
1006 | %%-------------------------------------------------------------------------
1007 |
1008 | -spec store(Key :: ?TYPE_NAME,
1009 | NewValue :: any(),
1010 | Node :: trie()) -> nonempty_trie().
1011 |
1012 | store(?TYPE_H0T0, NewValue, ?TYPE_EMPTY) ->
1013 | {H, H, {{T, NewValue}}};
1014 |
1015 | store(?TYPE_H0T0, NewValue, Node) ->
1016 | store_node(H, T, NewValue, Node).
1017 |
1018 | store_node(H, T, NewValue, {I0, I1, Data})
1019 | when is_integer(H), H < I0 ->
1020 | NewData = erlang:setelement(1,
1021 | tuple_move(I0 - H + 1, I1 - H + 1, Data, {?TYPE_EMPTY, error}),
1022 | {T, NewValue}),
1023 | {H, I1, NewData};
1024 |
1025 | store_node(H, T, NewValue, {I0, I1, Data})
1026 | when is_integer(H), H > I1 ->
1027 | N = H - I0 + 1,
1028 | NewData = erlang:setelement(N,
1029 | tuple_move(1, N, Data, {?TYPE_EMPTY, error}),
1030 | {T, NewValue}),
1031 | {I0, H, NewData};
1032 |
1033 | store_node(H, ?TYPE_EMPTY = T, NewValue, {I0, I1, Data})
1034 | when is_integer(H) ->
1035 | I = H - I0 + 1,
1036 | {Node, Value} = erlang:element(I, Data),
1037 | if
1038 | is_tuple(Node); Node =:= ?TYPE_EMPTY ->
1039 | {I0, I1, erlang:setelement(I, Data, {Node, NewValue})};
1040 | true ->
1041 | NewNode = {I0, I1, erlang:setelement(I, Data,
1042 | new_instance_state(Node, Value, error))},
1043 | store_node(H, T, NewValue, NewNode)
1044 | end;
1045 |
1046 | store_node(H, ?TYPE_H1T1 = T, NewValue, {I0, I1, Data})
1047 | when is_integer(H) ->
1048 | I = H - I0 + 1,
1049 | {Node, Value} = erlang:element(I, Data),
1050 | case Node of
1051 | {_, _, _} ->
1052 | {I0, I1, erlang:setelement(I, Data,
1053 | {store_node(H1, T1, NewValue, Node), Value})};
1054 | T ->
1055 | {I0, I1, erlang:setelement(I, Data, {Node, NewValue})};
1056 | ?TYPE_EMPTY ->
1057 | if
1058 | Value =:= error ->
1059 | {I0, I1, erlang:setelement(I, Data, {T, NewValue})};
1060 | true ->
1061 | {I0, I1, erlang:setelement(I, Data,
1062 | new_instance_state(T, NewValue, Value))}
1063 | end;
1064 | ?TYPE_BHBT ->
1065 | NewNode = {I0, I1,
1066 | erlang:setelement(I, Data, {{BH, BH, {{BT, Value}}}, error})},
1067 | store_node(H, T, NewValue, NewNode)
1068 | end.
1069 |
1070 | %%-------------------------------------------------------------------------
1071 | %% @doc
1072 | %% ===Take a value from the trie.===
1073 | %% @end
1074 | %%-------------------------------------------------------------------------
1075 |
1076 | -spec take(Key :: ?TYPE_NAME,
1077 | Node :: trie()) ->
1078 | {any(), trie()} | 'error'.
1079 |
1080 | take(_, ?TYPE_EMPTY) ->
1081 | error;
1082 |
1083 | take(?TYPE_H0T0, Node) ->
1084 | take_node(H, T, Node).
1085 |
1086 | take_node(H, _, {I0, I1, _})
1087 | when is_integer(H), H < I0;
1088 | is_integer(H), H > I1 ->
1089 | error;
1090 |
1091 | take_node(H, T, {I0, I1, Data})
1092 | when is_integer(H) ->
1093 | I = H - I0 + 1,
1094 | {Node, Value} = erlang:element(I, Data),
1095 | if
1096 | T == Node ->
1097 | if
1098 | Value =:= error ->
1099 | error;
1100 | true ->
1101 | {Value,
1102 | {I0, I1,
1103 | erlang:setelement(I, Data, {?TYPE_EMPTY, error})}}
1104 | end;
1105 | T =:= ?TYPE_EMPTY ->
1106 | if
1107 | Value =:= error ->
1108 | error;
1109 | true ->
1110 | {Value,
1111 | {I0, I1, erlang:setelement(I, Data, {Node, error})}}
1112 | end;
1113 | ?TYPE_CHECK(Node) ->
1114 | error;
1115 | is_tuple(Node) ->
1116 | ?TYPE_H1T1 = T,
1117 | case take_node(H1, T1, Node) of
1118 | error ->
1119 | error;
1120 | {OldValue, NewNode} ->
1121 | {OldValue,
1122 | {I0, I1, erlang:setelement(I, Data, {NewNode, Value})}}
1123 | end
1124 | end.
1125 |
1126 | %%-------------------------------------------------------------------------
1127 | %% @doc
1128 | %% ===Convert all entries in a trie to a list.===
1129 | %% The list is in alphabetical order.
1130 | %% @end
1131 | %%-------------------------------------------------------------------------
1132 |
1133 | -spec to_list(Node :: trie()) -> list({?TYPE_NAME, any()}).
1134 |
1135 | to_list(Node) ->
1136 | foldr(fun (Key, Value, L) -> [{Key, Value} | L] end, [], Node).
1137 |
1138 | %%-------------------------------------------------------------------------
1139 | %% @doc
1140 | %% ===Return a list of all entries within a trie that share a common prefix.===
1141 | %% @end
1142 | %%-------------------------------------------------------------------------
1143 |
1144 | -spec to_list_similar(Similar :: ?TYPE_NAME,
1145 | Node :: trie()) -> list({?TYPE_NAME, any()}).
1146 |
1147 | to_list_similar(Similar, Node) ->
1148 | foldr_similar(Similar,
1149 | fun(Key, Value, L) -> [{Key, Value} | L] end, [], Node).
1150 |
1151 | %%-------------------------------------------------------------------------
1152 | %% @doc
1153 | %% ===Update a value in a trie.===
1154 | %% @end
1155 | %%-------------------------------------------------------------------------
1156 |
1157 | -spec update(?TYPE_NAME,
1158 | F :: fun((any()) -> any()),
1159 | nonempty_trie()) -> nonempty_trie().
1160 |
1161 | update(?TYPE_H0T0, F, {_, _, _} = Node)
1162 | when is_function(F, 1) ->
1163 | update_node(H, T, F, Node).
1164 |
1165 | update_node(H, ?TYPE_EMPTY, F, {I0, I1, Data})
1166 | when is_integer(H), H >= I0, H =< I1 ->
1167 | I = H - I0 + 1,
1168 | {Node, Value} = erlang:element(I, Data),
1169 | if
1170 | is_tuple(Node); Node =:= ?TYPE_EMPTY, Value =/= error ->
1171 | {I0, I1, erlang:setelement(I, Data, {Node, F(Value)})}
1172 | end;
1173 |
1174 | update_node(H, T, F, {I0, I1, Data})
1175 | when is_integer(H), H >= I0, H =< I1 ->
1176 | I = H - I0 + 1,
1177 | {Node, Value} = erlang:element(I, Data),
1178 | case Node of
1179 | {_, _, _} ->
1180 | ?TYPE_H1T1 = T,
1181 | {I0, I1, erlang:setelement(I, Data,
1182 | {update_node(H1, T1, F, Node), Value})};
1183 | T ->
1184 | true = Value =/= error,
1185 | {I0, I1, erlang:setelement(I, Data, {Node, F(Value)})}
1186 | end.
1187 |
1188 | %%-------------------------------------------------------------------------
1189 | %% @doc
1190 | %% ===Update or add a value in a trie.===
1191 | %% @end
1192 | %%-------------------------------------------------------------------------
1193 |
1194 | -spec update(Key :: ?TYPE_NAME,
1195 | F :: fun((any()) -> any()),
1196 | Initial :: any(),
1197 | Node :: trie()) -> nonempty_trie().
1198 |
1199 | update(Key, _, Initial, ?TYPE_EMPTY = Node) ->
1200 | store(Key, Initial, Node);
1201 |
1202 | update(?TYPE_H0T0, F, Initial, {_, _, _} = Node)
1203 | when is_function(F, 1) ->
1204 | update_node(H, T, F, Initial, Node).
1205 |
1206 | update_node(H, T, _, Initial, {I0, I1, Data})
1207 | when is_integer(H), H < I0 ->
1208 | NewData = erlang:setelement(1,
1209 | tuple_move(I0 - H + 1, I1 - H + 1, Data, {?TYPE_EMPTY, error}),
1210 | {T, Initial}),
1211 | {H, I1, NewData};
1212 |
1213 | update_node(H, T, _, Initial, {I0, I1, Data})
1214 | when is_integer(H), H > I1 ->
1215 | N = H - I0 + 1,
1216 | NewData = erlang:setelement(N,
1217 | tuple_move(1, N, Data, {?TYPE_EMPTY, error}),
1218 | {T, Initial}),
1219 | {I0, H, NewData};
1220 |
1221 | update_node(H, ?TYPE_EMPTY = T, F, Initial, {I0, I1, Data})
1222 | when is_integer(H) ->
1223 | I = H - I0 + 1,
1224 | {Node, Value} = erlang:element(I, Data),
1225 | if
1226 | is_tuple(Node); Node =:= ?TYPE_EMPTY ->
1227 | if
1228 | Value =:= error ->
1229 | {I0, I1, erlang:setelement(I, Data, {Node, Initial})};
1230 | true ->
1231 | {I0, I1, erlang:setelement(I, Data, {Node, F(Value)})}
1232 | end;
1233 | true ->
1234 | ?TYPE_BHBT = Node,
1235 | NewNode = {I0, I1,
1236 | erlang:setelement(I, Data, {{BH, BH, {{BT, Value}}}, error})},
1237 | update_node(H, T, F, Initial, NewNode)
1238 | end;
1239 |
1240 | update_node(H, T, F, Initial, {I0, I1, Data})
1241 | when is_integer(H) ->
1242 | I = H - I0 + 1,
1243 | {Node, Value} = erlang:element(I, Data),
1244 | case Node of
1245 | {_, _, _} ->
1246 | ?TYPE_H1T1 = T,
1247 | {I0, I1, erlang:setelement(I, Data,
1248 | {update_node(H1, T1, F, Initial, Node), Value})};
1249 | T ->
1250 | {I0, I1, erlang:setelement(I, Data, {Node, F(Value)})};
1251 | ?TYPE_EMPTY ->
1252 | if
1253 | Value =:= error ->
1254 | {I0, I1, erlang:setelement(I, Data, {T, Initial})};
1255 | true ->
1256 | {I0, I1, erlang:setelement(I, Data,
1257 | new_instance_state(T, Initial, Value))}
1258 | end;
1259 | ?TYPE_BHBT ->
1260 | NewNode = {I0, I1,
1261 | erlang:setelement(I, Data, {{BH, BH, {{BT, Value}}}, error})},
1262 | update_node(H, T, F, Initial, NewNode)
1263 | end.
1264 |
1265 | %%-------------------------------------------------------------------------
1266 | %% @doc
1267 | %% ===Update a counter in a trie.===
1268 | %% @end
1269 | %%-------------------------------------------------------------------------
1270 |
1271 | -spec update_counter(Key :: ?TYPE_NAME,
1272 | Increment :: number(),
1273 | Node :: trie()) -> nonempty_trie().
1274 |
1275 | update_counter(Key, Increment, Node) ->
1276 | update(Key, fun(I) -> I + Increment end, Increment, Node).
1277 |
1278 |
--------------------------------------------------------------------------------
/src/trie_test.hrl:
--------------------------------------------------------------------------------
1 | %-*-Mode:erlang;coding:utf-8;tab-width:4;c-basic-offset:4;indent-tabs-mode:()-*-
2 | % ex: set ft=erlang fenc=utf-8 sts=4 ts=4 sw=4 et nomod:
3 | %%%
4 | %%%------------------------------------------------------------------------
5 | %%% trie eunit common functionality
6 | %%%
7 | %%% MIT License
8 | %%%
9 | %%% Copyright (c) 2020 Michael Truog
10 | %%%
11 | %%% Permission is hereby granted, free of charge, to any person obtaining a
12 | %%% copy of this software and associated documentation files (the "Software"),
13 | %%% to deal in the Software without restriction, including without limitation
14 | %%% the rights to use, copy, modify, merge, publish, distribute, sublicense,
15 | %%% and/or sell copies of the Software, and to permit persons to whom the
16 | %%% Software is furnished to do so, subject to the following conditions:
17 | %%%
18 | %%% The above copyright notice and this permission notice shall be included in
19 | %%% all copies or substantial portions of the Software.
20 | %%%
21 | %%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 | %%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 | %%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24 | %%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 | %%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26 | %%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
27 | %%% DEALINGS IN THE SOFTWARE.
28 | %%%
29 | %%%------------------------------------------------------------------------
30 |
31 | -ifndef(_assertOk).
32 | -define(_assertOk(Expr), ?_assertEqual(ok, Expr)).
33 | -endif.
34 |
35 | -ifdef(CLOUDI_TEST_TIMEOUT).
36 | -define(TEST_TIMEOUT, ?CLOUDI_TEST_TIMEOUT). % seconds
37 | -else.
38 | -define(TEST_TIMEOUT, 10). % seconds
39 | -endif.
40 | -ifndef(CLOUDI_LONG_TEST_TIMEOUT).
41 | -define(CLOUDI_LONG_TEST_TIMEOUT, 60). % minutes
42 | -endif.
43 |
44 | -compile({nowarn_unused_function,
45 | [{test_condition, 2}]}).
46 |
47 | test_condition(_, 0) ->
48 | [];
49 | test_condition(L, LongTestTimeout)
50 | when LongTestTimeout > 0 ->
51 | {timeout, LongTestTimeout * 60, L}.
52 |
53 |
--------------------------------------------------------------------------------
/test/proper_srv.erl:
--------------------------------------------------------------------------------
1 | %-*-Mode:erlang;coding:utf-8;tab-width:4;c-basic-offset:4;indent-tabs-mode:()-*-
2 | % ex: set ft=erlang fenc=utf-8 sts=4 ts=4 sw=4 et nomod:
3 | %%%
4 | %%%------------------------------------------------------------------------
5 | %%% @doc
6 | %%% Test the dict API functions on an ordered collection.
7 | %%% @end
8 | %%%
9 | %%% @author Michael Truog
10 | %%% @copyright 2012 Michael Truog
11 | %%%------------------------------------------------------------------------
12 |
13 | -module(proper_srv).
14 |
15 | -behaviour(gen_server).
16 |
17 | %% external interface
18 | -export([start_link/1,
19 | stop/0]).
20 |
21 | %% data structure API
22 | -export([append/2,
23 | append_list/2,
24 | erase/1,
25 | fetch_keys/0,
26 | filter/1,
27 | find/1,
28 | fold/2,
29 | is_key/1,
30 | map/1,
31 | size/0,
32 | store/2,
33 | update/3]).
34 |
35 | %% gen_server callbacks
36 | -export([init/1,
37 | handle_call/3, handle_cast/2, handle_info/2,
38 | terminate/2, code_change/3]).
39 |
40 | -record(state,
41 | {
42 | module,
43 | data_structure
44 | }).
45 |
46 | %%%------------------------------------------------------------------------
47 | %%% External interface functions
48 | %%%------------------------------------------------------------------------
49 |
50 | start_link(Module) ->
51 | gen_server:start_link({local, ?MODULE}, ?MODULE, [Module], []).
52 |
53 | stop() ->
54 | gen_server:stop(?MODULE).
55 |
56 | %%%------------------------------------------------------------------------
57 | %%% Data structure API
58 | %%%------------------------------------------------------------------------
59 |
60 | append(Key, Value) ->
61 | call(append, [Key, Value]).
62 |
63 | append_list(Key, L) ->
64 | call(append_list, [Key, L]).
65 |
66 | erase(Key) ->
67 | call(erase, [Key]).
68 |
69 | fetch_keys() ->
70 | call(fetch_keys, []).
71 |
72 | filter(F) ->
73 | call(filter, [F]).
74 |
75 | find(Key) ->
76 | call(find, [Key]).
77 |
78 | fold(F, Acc) ->
79 | call(fold, [F, Acc]).
80 |
81 | is_key(Key) ->
82 | call(is_key, [Key]).
83 |
84 | map(F) ->
85 | call(map, [F]).
86 |
87 | size() ->
88 | call(size, []).
89 |
90 | store(Key, L) ->
91 | call(store, [Key, L]).
92 |
93 | update(Key, F, L) ->
94 | call(update, [Key, F, L]).
95 |
96 | %%%------------------------------------------------------------------------
97 | %%% Callback functions from gen_server
98 | %%%------------------------------------------------------------------------
99 |
100 | init([Module]) ->
101 | {ok, #state{module = Module,
102 | data_structure = Module:new()}}.
103 |
104 | handle_call({F, A}, _,
105 | #state{data_structure = D,
106 | module = M} = S) when F =:= filter ->
107 | Result = erlang:apply(M, F, A ++ [D]),
108 | {reply, M:to_list(Result), S};
109 | handle_call({F, A}, _,
110 | #state{data_structure = D,
111 | module = M} = S) when F =:= fetch_keys;
112 | F =:= find;
113 | F =:= fold;
114 | F =:= is_key;
115 | F =:= size ->
116 | Result = erlang:apply(M, F, A ++ [D]),
117 | {reply, Result, S};
118 | handle_call({F, A}, _,
119 | #state{data_structure = D,
120 | module = M} = S) ->
121 | Result = M:to_list(D),
122 | NewD = erlang:apply(M, F, A ++ [D]),
123 | {reply, Result, S#state{data_structure = NewD}};
124 | handle_call(_, _, State) ->
125 | {stop, invalid_call, error, State}.
126 |
127 | handle_cast(_Msg, State) ->
128 | {stop, invalid_cast, State}.
129 |
130 | handle_info(_Info, State) ->
131 | {stop, invalid_info, State}.
132 |
133 | terminate(_Reason, _State) ->
134 | ok.
135 |
136 | code_change(_OldVsn, State, _Extra) ->
137 | {ok, State}.
138 |
139 | %%%------------------------------------------------------------------------
140 | %%% Private functions
141 | %%%------------------------------------------------------------------------
142 |
143 | call(Command, Args) ->
144 | gen_server:call(?MODULE, {Command, Args}, infinity).
145 |
146 |
--------------------------------------------------------------------------------
/test/trie_proper.erl:
--------------------------------------------------------------------------------
1 | %-*-Mode:erlang;coding:utf-8;tab-width:4;c-basic-offset:4;indent-tabs-mode:()-*-
2 | % ex: set ft=erlang fenc=utf-8 sts=4 ts=4 sw=4 et nomod:
3 |
4 | -module(trie_proper).
5 | -ifdef(TEST).
6 | -behaviour(proper_statem).
7 | -include_lib("proper/include/proper.hrl").
8 |
9 | %% external interface
10 | -export([qc_run/1, correct/1]).
11 |
12 | %% proper_statem callbacks
13 | -export([command/1, initial_state/0, next_state/3,
14 | postcondition/3, precondition/2]).
15 |
16 | -record(state,
17 | {
18 | dict
19 | }).
20 | -define(SERVER, proper_srv).
21 |
22 | %%%------------------------------------------------------------------------
23 | %%% External interface functions
24 | %%%------------------------------------------------------------------------
25 |
26 | qc_opts() ->
27 | [{numtests, 10000}].
28 |
29 | qc_run(M) when is_atom(M) ->
30 | proper:quickcheck(trie_proper:correct(M), qc_opts()).
31 |
32 | %%%------------------------------------------------------------------------
33 | %%% Callback functions from proper_statem
34 | %%%------------------------------------------------------------------------
35 |
36 | initial_state() ->
37 | #state{dict = dict:new()}.
38 |
39 | % dict functions not tested:
40 | % fetch/2 from_list/1 map/2 update/3 update_counter/3
41 | command(#state{}) ->
42 | oneof([{call, ?SERVER, append, [key(), value()]},
43 | {call, ?SERVER, append_list, [key(), [value(), value()]]},
44 | {call, ?SERVER, erase, [key()]},
45 | {call, ?SERVER, fetch_keys, []},
46 | {call, ?SERVER, filter, [fun(K, _) -> K > key() end]},
47 | {call, ?SERVER, find, [key()]},
48 | {call, ?SERVER, fold, [fun(_, V, S) -> lists:sum(V) + S end, 0]},
49 | {call, ?SERVER, is_key, [key()]},
50 | {call, ?SERVER, map, [fun(_, V) ->
51 | lists:map(fun(I) -> I + 1 end, V)
52 | end]},
53 | {call, ?SERVER, size, []},
54 | {call, ?SERVER, store, [key(), [value()]]},
55 | {call, ?SERVER, update, [key(),
56 | fun(V) ->
57 | lists:map(fun(I) -> I + 1 end, V)
58 | end,
59 | [value()]]}]).
60 |
61 | next_state(#state{dict = D} = S, _V, {call, _, append, [Key, Value]}) ->
62 | S#state{dict = dict:append(Key, Value, D)};
63 | next_state(#state{dict = D} = S, _V, {call, _, append_list, [Key, L]}) ->
64 | S#state{dict = dict:append_list(Key, L, D)};
65 | next_state(#state{dict = D} = S, _V, {call, _, erase, [Key]}) ->
66 | S#state{dict = dict:erase(Key, D)};
67 | next_state(S, _V, {call, _, fetch_keys, _}) ->
68 | S;
69 | next_state(S, _V, {call, _, filter, [_F]}) ->
70 | S;
71 | next_state(S, _V, {call, _, find, [_Key]}) ->
72 | S;
73 | next_state(S, _V, {call, _, fold, [_F, _Acc]}) ->
74 | S;
75 | next_state(S, _V, {call, _, is_key, [_Key]}) ->
76 | S;
77 | next_state(#state{dict = D} = S, _V, {call, _, map, [F]}) ->
78 | S#state{dict = dict:map(F, D)};
79 | next_state(S, _V, {call, _, size, _}) ->
80 | S;
81 | next_state(#state{dict = D} = S, _V, {call, _, store, [Key, L]}) ->
82 | S#state{dict = dict:store(Key, L, D)};
83 | next_state(#state{dict = D} = S, _V, {call, _, update, [Key, F, L]}) ->
84 | S#state{dict = dict:update(Key, F, L, D)}.
85 |
86 | precondition(_S, _Call) ->
87 | true. % No limitation on the things we can call at all.
88 |
89 | postcondition(#state{dict = D}, {call, _, filter, [F]}, R) ->
90 | R == lists:keysort(1, dict:to_list(dict:filter(F, D)));
91 | postcondition(#state{dict = D}, {call, _, fetch_keys, []}, R) ->
92 | R == lists:sort(dict:fetch_keys(D));
93 | postcondition(#state{dict = D}, {call, _, find, [Key]}, R) ->
94 | R == dict:find(Key, D);
95 | postcondition(#state{dict = D}, {call, _, fold, [F, Acc]}, R) ->
96 | R == dict:fold(F, Acc, D);
97 | postcondition(#state{dict = D}, {call, _, is_key, [Key]}, R) ->
98 | R == dict:is_key(Key, D);
99 | postcondition(#state{dict = D}, {call, _, size, []}, R) ->
100 | R == dict:size(D);
101 | postcondition(#state{dict = D}, {call, _, _F, _A}, R) ->
102 | R == lists:keysort(1, dict:to_list(D));
103 | postcondition(_, _, _) ->
104 | false.
105 |
106 | correct(M) ->
107 | ?FORALL(Cmds, commands(?MODULE),
108 | ?TRAPEXIT(
109 | begin
110 | ?SERVER:start_link(M),
111 | {History,State,Result} = run_commands(?MODULE, Cmds),
112 | ?SERVER:stop(),
113 | ?WHENFAIL(io:format("History: ~w\nState: ~w\nResult: ~w\n",
114 | [History, State, Result]),
115 | aggregate(command_names(Cmds), Result =:= ok))
116 | end)).
117 |
118 | %%%------------------------------------------------------------------------
119 | %%% Private functions
120 | %%%------------------------------------------------------------------------
121 |
122 | % random 8 character string
123 | key() ->
124 | fixed_list([integer(48, 126),
125 | integer(48, 126),
126 | integer(48, 126),
127 | integer(48, 126),
128 | integer(48, 126),
129 | integer(48, 126),
130 | integer(48, 126),
131 | integer(48, 126)]).
132 |
133 | value() ->
134 | integer().
135 |
136 | -endif.
137 |
--------------------------------------------------------------------------------