├── 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 |

A trie data structure implementation.

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 |

Description

29 |

A trie data structure implementation.

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 |

Data Types

41 | 42 |

empty_trie()

43 |

empty_trie() = <<>>

44 | 45 | 46 |

nonempty_trie()

47 |

nonempty_trie() = {integer(), integer(), tuple()}

48 | 49 | 50 |

trie()

51 |

trie() = nonempty_trie() | empty_trie()

52 | 53 | 54 |

Function Index

55 | 57 | 59 | 61 | 63 | 65 | 67 | 69 | 71 | 73 | 78 | 80 | 83 | 86 | 89 | 92 | 95 | 98 | 101 | 104 | 106 | 108 | 111 | 115 | 117 | 122 | 125 | 127 | 129 | 131 | 133 | 136 | 138 | 140 | 142 | 144 |
append/3 56 |

Append a value as a list element in a trie instance.

.
append_list/3 58 |

Append a list of values as a list element in a trie instance.

.
erase/2 60 |

Erase a value in a trie.

.
erase_similar/2 62 |

Erase all entries within a trie that share a common prefix.

.
fetch/2 64 |

Fetch a value from a trie.

.
fetch_keys/1 66 |

Fetch all the keys in a trie.

.
fetch_keys_similar/2 68 |

Fetch the keys within a trie that share a common prefix.

.
filter/2 70 |

Filter a trie with a predicate function.

.
find/2 72 |

Find a value in a trie.

.
find_prefix/2 74 |

Find a value in a trie by prefix.

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.
find_prefix_longest/2 79 |

Find the longest key in a trie that is a prefix to the passed string.

.
find_prefixes/2 81 |

Find all the keys in a trie that are prefixes to the passed string.

82 | The entries are returned in alphabetical order.
fold/3 84 |

Fold a function over the trie.

85 | Traverses in alphabetical order.
fold_similar/4 87 |

Fold a function over the keys within a trie that share a common prefix.

88 | Traverses in alphabetical order.
foldl/3 90 |

Fold a function over the trie.

91 | Traverses in alphabetical order.
foldl_similar/4 93 |

Fold a function over the keys within a trie that share a common prefix.

94 | Traverses in alphabetical order.
foldr/3 96 |

Fold a function over the trie in reverse.

97 | Traverses in reverse alphabetical order.
foldr_similar/4 99 |

Fold a function over the keys within a trie that share a common prefix in reverse.

100 | Traverses in reverse alphabetical order.
foreach/2 102 |

Call a function for each element.

103 | Traverses in alphabetical order.
from_list/1 105 |

Create a trie from a list.

.
is_key/2 107 |

Determine if a key exists in a trie.

.
map/2 109 |

Map a function over a trie.

110 | Traverses in reverse alphabetical order.
merge/3 112 |

Merge two trie instance.

113 | Update the second trie parameter with all of the elements 114 | found within the first trie parameter.
new/0 116 |

Create a new trie instance.

.
new/1 118 |

Create a new trie instance from a list.

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).
prefix/3 123 |

Insert a value as the first list element in a trie instance.

124 | The reverse of append/3.
size/1 126 |

Size of a trie instance.

.
store/2 128 |

Store only a key in a trie instance.

.
store/3 130 |

Store a key/value pair in a trie instance.

.
take/2 132 |

Take a value from the trie.

.
to_list/1 134 |

Convert all entries in a trie to a list.

135 | The list is in alphabetical order.
to_list_similar/2 137 |

Return a list of all entries within a trie that share a common prefix.

.
update/3 139 |

Update a value in a trie.

.
update/4 141 |

Update or add a value in a trie.

.
update_counter/3 143 |

Update a counter in a trie.

.
145 | 146 |

Function Details

147 | 148 |

append/3

149 |
150 |

append(Key::<<_:8, _:_*8>>, Value::any(), Node::trie()) -> nonempty_trie()

151 |

152 |

153 |

Append a value as a list element in a trie instance.

154 |

155 | 156 |

append_list/3

157 |
158 |

append_list(Key::<<_:8, _:_*8>>, ValueList::list(), Node::trie()) -> nonempty_trie()

159 |

160 |

161 |

Append a list of values as a list element in a trie instance.

162 |

163 | 164 |

erase/2

165 |
166 |

erase(Key::<<_:8, _:_*8>>, Node::trie()) -> trie()

167 |

168 |

169 |

Erase a value in a trie.

170 |

171 | 172 |

erase_similar/2

173 |
174 |

erase_similar(Similar::<<_:8, _:_*8>>, Node::trie()) -> [<<_:8, _:_*8>>]

175 |

176 |

177 |

Erase all entries within a trie that share a common prefix.

178 |

179 | 180 |

fetch/2

181 |
182 |

fetch(X1::<<_:8, _:_*8>>, Node::nonempty_trie()) -> any()

183 |

184 |

185 |

Fetch a value from a trie.

186 |

187 | 188 |

fetch_keys/1

189 |
190 |

fetch_keys(Node::trie()) -> [<<_:8, _:_*8>>]

191 |

192 |

193 |

Fetch all the keys in a trie.

194 |

195 | 196 |

fetch_keys_similar/2

197 |
198 |

fetch_keys_similar(Similar::<<_:8, _:_*8>>, Node::trie()) -> [<<_:8, _:_*8>>]

199 |

200 |

201 |

Fetch the keys within a trie that share a common prefix.

202 |

203 | 204 |

filter/2

205 |
206 |

filter(F::fun((<<_:8, _:_*8>>, any()) -> boolean()), Node::trie()) -> trie()

207 |

208 |

209 |

Filter a trie with a predicate function.

210 |

211 | 212 |

find/2

213 |
214 |

find(X1::<<_:8, _:_*8>>, Node::trie()) -> {ok, any()} | error

215 |

216 |

217 |

Find a value in a trie.

218 |

219 | 220 |

find_prefix/2

221 |
222 |

find_prefix(X1::<<_:8, _:_*8>>, X2::trie()) -> {ok, any()} | prefix | error

223 |

224 |

225 |

Find a value in a trie by prefix.

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 |

find_prefix_longest/2

231 |
232 |

find_prefix_longest(Match::<<_:8, _:_*8>>, Node::trie()) -> {ok, <<_:8, _:_*8>>, any()} | error

233 |

234 |

235 |

Find the longest key in a trie that is a prefix to the passed string.

236 |

237 | 238 |

find_prefixes/2

239 |
240 |

find_prefixes(Match::<<_:8, _:_*8>>, Node::trie()) -> [{<<_:8, _:_*8>>, any()}]

241 |

242 |

243 |

Find all the keys in a trie that are prefixes to the passed string.

244 | The entries are returned in alphabetical order.

245 | 246 |

fold/3

247 |
248 |

fold(F::fun((<<_:8, _:_*8>>, any(), any()) -> any()), A::any(), Node::trie()) -> any()

249 |

250 |

251 |

Fold a function over the trie.

252 | Traverses in alphabetical order.

253 | 254 |

fold_similar/4

255 |
256 |

fold_similar(Similar::<<_:8, _:_*8>>, F::fun((<<_:8, _:_*8>>, any(), any()) -> any()), A::any(), Node::trie()) -> any()

257 |

258 |

259 |

Fold a function over the keys within a trie that share a common prefix.

260 | Traverses in alphabetical order.

261 | 262 |

foldl/3

263 |
264 |

foldl(F::fun((<<_:8, _:_*8>>, any(), any()) -> any()), A::any(), Node::trie()) -> any()

265 |

266 |

267 |

Fold a function over the trie.

268 | Traverses in alphabetical order.

269 | 270 |

foldl_similar/4

271 |
272 |

foldl_similar(Similar::<<_:8, _:_*8>>, F::fun((<<_:8, _:_*8>>, any(), any()) -> any()), A::any(), Node::trie()) -> any()

273 |

274 |

275 |

Fold a function over the keys within a trie that share a common prefix.

276 | Traverses in alphabetical order.

277 | 278 |

foldr/3

279 |
280 |

foldr(F::fun((<<_:8, _:_*8>>, any(), any()) -> any()), A::any(), Node::trie()) -> any()

281 |

282 |

283 |

Fold a function over the trie in reverse.

284 | Traverses in reverse alphabetical order.

285 | 286 |

foldr_similar/4

287 |
288 |

foldr_similar(Similar::<<_:8, _:_*8>>, F::fun((<<_:8, _:_*8>>, any(), any()) -> any()), A::any(), Node::trie()) -> any()

289 |

290 |

291 |

Fold a function over the keys within a trie that share a common prefix in reverse.

292 | Traverses in reverse alphabetical order.

293 | 294 |

foreach/2

295 |
296 |

foreach(F::fun((<<_:8, _:_*8>>, any()) -> any()), Node::trie()) -> any()

297 |

298 |

299 |

Call a function for each element.

300 | Traverses in alphabetical order.

301 | 302 |

from_list/1

303 |
304 |

from_list(L::[<<_:8, _:_*8>> | tuple()]) -> trie()

305 |

306 |

307 |

Create a trie from a list.

308 |

309 | 310 |

is_key/2

311 |
312 |

is_key(X1::<<_:8, _:_*8>>, Node::trie()) -> boolean()

313 |

314 |

315 |

Determine if a key exists in a trie.

316 |

317 | 318 |

map/2

319 |
320 |

map(F::fun((<<_:8, _:_*8>>, any()) -> any()), Node::trie()) -> trie()

321 |

322 |

323 |

Map a function over a trie.

324 | Traverses in reverse alphabetical order.

325 | 326 |

merge/3

327 |
328 |

merge(F::fun((<<_:8, _:_*8>>, any(), any()) -> any()), Node1::trie(), Node2::trie()) -> trie()

329 |

330 |

331 |

Merge two trie instance.

332 | Update the second trie parameter with all of the elements 333 | found within the first trie parameter.

334 | 335 |

new/0

336 |
337 |

new() -> empty_trie()

338 |

339 |

340 |

Create a new trie instance.

341 |

342 | 343 |

new/1

344 |
345 |

new(L::[<<_:8, _:_*8>> | tuple()]) -> trie()

346 |

347 |

348 |

Create a new trie instance from a list.

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 |

prefix/3

356 |
357 |

prefix(Key::<<_:8, _:_*8>>, Value::any(), Node::trie()) -> nonempty_trie()

358 |

359 |

360 |

Insert a value as the first list element in a trie instance.

361 | The reverse of append/3.

362 | 363 |

size/1

364 |
365 |

size(Node::trie()) -> non_neg_integer()

366 |

367 |

368 |

Size of a trie instance.

369 |

370 | 371 |

store/2

372 |
373 |

store(Key::<<_:8, _:_*8>>, Node::trie()) -> nonempty_trie()

374 |

375 |

376 |

Store only a key in a trie instance.

377 |

378 | 379 |

store/3

380 |
381 |

store(Key::<<_:8, _:_*8>>, NewValue::any(), Node::trie()) -> nonempty_trie()

382 |

383 |

384 |

Store a key/value pair in a trie instance.

385 |

386 | 387 |

take/2

388 |
389 |

take(Key::<<_:8, _:_*8>>, Node::trie()) -> {any(), trie()} | error

390 |

391 |

392 |

Take a value from the trie.

393 |

394 | 395 |

to_list/1

396 |
397 |

to_list(Node::trie()) -> [{<<_:8, _:_*8>>, any()}]

398 |

399 |

400 |

Convert all entries in a trie to a list.

401 | The list is in alphabetical order.

402 | 403 |

to_list_similar/2

404 |
405 |

to_list_similar(Similar::<<_:8, _:_*8>>, Node::trie()) -> [{<<_:8, _:_*8>>, any()}]

406 |

407 |

408 |

Return a list of all entries within a trie that share a common prefix.

409 |

410 | 411 |

update/3

412 |
413 |

update(X1::<<_:8, _:_*8>>, F::fun((any()) -> any()), Node::nonempty_trie()) -> nonempty_trie()

414 |

415 |

416 |

Update a value in a trie.

417 |

418 | 419 |

update/4

420 |
421 |

update(Key::<<_:8, _:_*8>>, F::fun((any()) -> any()), Initial::any(), Node::trie()) -> nonempty_trie()

422 |

423 |

424 |

Update or add a value in a trie.

425 |

426 | 427 |

update_counter/3

428 |
429 |

update_counter(Key::<<_:8, _:_*8>>, Increment::number(), Node::trie()) -> nonempty_trie()

430 |

431 |

432 |

Update a counter in a trie.

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 | <h2>This page uses frames</h2> 12 | <p>Your browser does not accept frames. 13 | <br>You should go to the <a href="overview-summary.html">non-frame version</a> instead. 14 | </p> 15 | 16 | 17 | -------------------------------------------------------------------------------- /doc/modules-frame.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The trie application 5 | 6 | 7 | 8 |

Modules

9 | 10 | 11 |
btrie
trie
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 |

A trie data structure implementation.

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 |

Description

31 |

A trie data structure implementation.

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 |

Data Types

45 | 46 |

empty_trie()

47 |

empty_trie() = []

48 | 49 | 50 |

nonempty_trie()

51 |

nonempty_trie() = {integer(), integer(), tuple()}

52 | 53 | 54 |

trie()

55 |

trie() = nonempty_trie() | empty_trie()

56 | 57 | 58 |

Function Index

59 | 61 | 63 | 65 | 67 | 69 | 71 | 73 | 75 | 77 | 81 | 85 | 90 | 92 | 95 | 98 | 101 | 104 | 107 | 110 | 113 | 116 | 119 | 122 | 124 | 126 | 128 | 130 | 133 | 136 | 139 | 142 | 146 | 148 | 152 | 155 | 158 | 161 | 165 | 167 | 172 | 177 | 182 | 185 | 188 | 191 | 195 | 199 | 202 | 205 | 208 | 211 | 213 | 215 | 217 | 219 | 222 | 224 | 226 | 228 | 230 |
append/3 60 |

Append a value as a list element in a trie instance.

.
append_list/3 62 |

Append a list of values as a list element in a trie instance.

.
erase/2 64 |

Erase a value in a trie.

.
erase_similar/2 66 |

Erase all entries within a trie that share a common prefix.

.
fetch/2 68 |

Fetch a value from a trie.

.
fetch_keys/1 70 |

Fetch all the keys in a trie.

.
fetch_keys_similar/2 72 |

Fetch the keys within a trie that share a common prefix.

.
filter/2 74 |

Filter a trie with a predicate function.

.
find/2 76 |

Find a value in a trie.

.
find_match/2 78 |

Find a match with patterns held within a trie.

79 | All patterns held within the trie use a wildcard character "*" to represent 80 | a regex of ".+".
find_match2/2 82 |

Find a match with patterns (using 2 wildcard characters) held within a trie.

83 | All patterns held within the trie use the wildcard character "*" or "?" 84 | to represent a regex of ".+".
find_prefix/2 86 |

Find a value in a trie by prefix.

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.
find_prefix_longest/2 91 |

Find the longest key in a trie that is a prefix to the passed string.

.
find_prefixes/2 93 |

Find all the keys in a trie that are prefixes to the passed string.

94 | The entries are returned in alphabetical order.
find_similar/2 96 |

Find the first key/value pair in a trie where the key shares a common prefix.

97 | The first match is found based on alphabetical order.
fold/3 99 |

Fold a function over the trie.

100 | Traverses in alphabetical order.
fold_match/4 102 |

Fold a function over the keys within a trie that matches a pattern.

103 | Traverses in alphabetical order.
fold_similar/4 105 |

Fold a function over the keys within a trie that share a common prefix.

106 | Traverses in alphabetical order.
foldl/3 108 |

Fold a function over the trie.

109 | Traverses in alphabetical order.
foldl_similar/4 111 |

Fold a function over the keys within a trie that share a common prefix.

112 | Traverses in alphabetical order.
foldr/3 114 |

Fold a function over the trie in reverse.

115 | Traverses in reverse alphabetical order.
foldr_similar/4 117 |

Fold a function over the keys within a trie that share a common prefix in reverse.

118 | Traverses in reverse alphabetical order.
foreach/2 120 |

Call a function for each element.

121 | Traverses in alphabetical order.
from_list/1 123 |

Create a trie from a list.

.
is_bytestring/1 125 |

Test if the parameter is a byte string.

.
is_bytestring_nonempty/1 127 |

Test if the parameter is a nonempty byte string.

.
is_key/2 129 |

Determine if a key exists in a trie.

.
is_pattern/1 131 |

Test to determine if a string is a pattern.

132 | "*" is the wildcard character (equivalent to the ".+" regex).
is_pattern2/1 134 |

Test to determine if a string is a pattern (using 2 wildcard characters).

135 | "*" and "?" are wildcard characters (equivalent to the ".+" regex).
is_pattern2_bytes/1 137 |

Test to determine if a byte string is a pattern (using 2 wildcard characters).

138 | "*" and "?" are wildcard characters (equivalent to the ".+" regex).
is_pattern_bytes/1 140 |

Test to determine if a byte string is a pattern.

141 | "*" is the wildcard character (equivalent to the ".+" regex).
is_prefix/2 143 |

Determine if the prefix provided has existed within a trie.

144 | The function returns true if the string supplied is a prefix 145 | for a key that has previously been stored within the trie.
is_prefixed/2 147 |

Determine if the provided string has a prefix within a trie.

.
is_prefixed/3 149 |

Determine if the provided string has an acceptable prefix within a trie.

150 | The prefix within the trie must match at least 1 character that is not 151 | within the excluded list of characters.
iter/2 153 |

Iterate over a trie.

154 | Traverses in alphabetical order.
itera/3 156 |

Iterate over a trie with an accumulator.

157 | Traverses in alphabetical order.
map/2 159 |

Map a function over a trie.

160 | Traverses in reverse alphabetical order.
merge/3 162 |

Merge two trie instance.

163 | Update the second trie parameter with all of the elements 164 | found within the first trie parameter.
new/0 166 |

Create a new trie instance.

.
new/1 168 |

Create a new trie instance from a list.

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).
pattern2_fill/2 173 |

Fill wildcard characters in a string.

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).
pattern2_fill/4 178 |

Fill wildcard characters in a string.

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).
pattern2_parse/2 183 |

Parse a string based on the supplied wildcard pattern (using 2 wildcard characters).

184 | "*" and "?" are wildcard characters (equivalent to the ".+" regex).
pattern2_parse/3 186 |

Parse a string based on the supplied wildcard pattern (using 2 wildcard characters).

187 | "*" and "?" are wildcard characters (equivalent to the ".+" regex).
pattern2_suffix/2 189 |

Parse a string based on the supplied wildcard pattern (using 2 wildcard characters) to return only the suffix after the pattern.

190 | "*" and "?" are wildcard characters (equivalent to the ".+" regex).
pattern_fill/2 192 |

Fill wildcard characters in a string.

193 | The "*" wildcard character may be used consecutively by this function 194 | to have parameters concatenated.
pattern_fill/4 196 |

Fill wildcard characters in a string.

197 | The "*" wildcard character may be used consecutively by this function 198 | to have parameters concatenated.
pattern_parse/2 200 |

Parse a string based on the supplied wildcard pattern.

201 | "*" is the wildcard character (equivalent to the ".+" regex).
pattern_parse/3 203 |

Parse a string based on the supplied wildcard pattern.

204 | "*" is the wildcard character (equivalent to the ".+" regex).
pattern_suffix/2 206 |

Parse a string based on the supplied wildcard pattern to return only the suffix after the pattern.

207 | "*" is the wildcard character (equivalent to the ".+" regex).
prefix/3 209 |

Insert a value as the first list element in a trie instance.

210 | The reverse of append/3.
size/1 212 |

Size of a trie instance.

.
store/2 214 |

Store only a key in a trie instance.

.
store/3 216 |

Store a key/value pair in a trie instance.

.
take/2 218 |

Take a value from the trie.

.
to_list/1 220 |

Convert all entries in a trie to a list.

221 | The list is in alphabetical order.
to_list_similar/2 223 |

Return a list of all entries within a trie that share a common prefix.

.
update/3 225 |

Update a value in a trie.

.
update/4 227 |

Update or add a value in a trie.

.
update_counter/3 229 |

Update a counter in a trie.

.
231 | 232 |

Function Details

233 | 234 |

append/3

235 |
236 |

append(Key::nonempty_string(), Value::any(), Node::trie()) -> nonempty_trie()

237 |

238 |

239 |

Append a value as a list element in a trie instance.

240 |

241 | 242 |

append_list/3

243 |
244 |

append_list(Key::nonempty_string(), ValueList::list(), Node::trie()) -> nonempty_trie()

245 |

246 |

247 |

Append a list of values as a list element in a trie instance.

248 |

249 | 250 |

erase/2

251 |
252 |

erase(Key::nonempty_string(), Node::trie()) -> trie()

253 |

254 |

255 |

Erase a value in a trie.

256 |

257 | 258 |

erase_similar/2

259 |
260 |

erase_similar(Similar::nonempty_string(), Node::trie()) -> [nonempty_string()]

261 |

262 |

263 |

Erase all entries within a trie that share a common prefix.

264 |

265 | 266 |

fetch/2

267 |
268 |

fetch(T::nonempty_string(), Node::nonempty_trie()) -> any()

269 |

270 |

271 |

Fetch a value from a trie.

272 |

273 | 274 |

fetch_keys/1

275 |
276 |

fetch_keys(Node::trie()) -> [nonempty_string()]

277 |

278 |

279 |

Fetch all the keys in a trie.

280 |

281 | 282 |

fetch_keys_similar/2

283 |
284 |

fetch_keys_similar(Similar::nonempty_string(), Node::trie()) -> [nonempty_string()]

285 |

286 |

287 |

Fetch the keys within a trie that share a common prefix.

288 |

289 | 290 |

filter/2

291 |
292 |

filter(F::fun((nonempty_string(), any()) -> boolean()), Node::trie()) -> trie()

293 |

294 |

295 |

Filter a trie with a predicate function.

296 |

297 | 298 |

find/2

299 |
300 |

find(T::nonempty_string(), Node::trie()) -> {ok, any()} | error

301 |

302 |

303 |

Find a value in a trie.

304 |

305 | 306 |

find_match/2

307 |
308 |

find_match(Match::string(), Node::trie()) -> {ok, any(), any()} | error

309 |

310 |

311 |

Find a match with patterns held within a trie.

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 |

find_match2/2

321 |
322 |

find_match2(Match::string(), Node::trie()) -> {ok, any(), any()} | error

323 |

324 |

325 |

Find a match with patterns (using 2 wildcard characters) held within a trie.

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 |

find_prefix/2

337 |
338 |

find_prefix(T::nonempty_string(), X2::trie()) -> {ok, any()} | prefix | error

339 |

340 |

341 |

Find a value in a trie by prefix.

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 |

find_prefix_longest/2

347 |
348 |

find_prefix_longest(Match::nonempty_string(), Node::trie()) -> {ok, nonempty_string(), any()} | error

349 |

350 |

351 |

Find the longest key in a trie that is a prefix to the passed string.

352 |

353 | 354 |

find_prefixes/2

355 |
356 |

find_prefixes(Match::nonempty_string(), Node::trie()) -> [{nonempty_string(), any()}]

357 |

358 |

359 |

Find all the keys in a trie that are prefixes to the passed string.

360 | The entries are returned in alphabetical order.

361 | 362 |

find_similar/2

363 |
364 |

find_similar(Similar::string(), Node::trie()) -> {ok, string(), any()} | error

365 |

366 |

367 |

Find the first key/value pair in a trie where the key shares a common prefix.

368 | The first match is found based on alphabetical order.

369 | 370 |

fold/3

371 |
372 |

fold(F::fun((nonempty_string(), any(), any()) -> any()), A::any(), Node::trie()) -> any()

373 |

374 |

375 |

Fold a function over the trie.

376 | Traverses in alphabetical order.

377 | 378 |

fold_match/4

379 |
380 |

fold_match(Match::string(), F::fun((string(), any(), any()) -> any()), A::any(), Node::trie()) -> any()

381 |

382 |

383 |

Fold a function over the keys within a trie that matches a pattern.

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 |

fold_similar/4

392 |
393 |

fold_similar(Similar::nonempty_string(), F::fun((nonempty_string(), any(), any()) -> any()), A::any(), Node::trie()) -> any()

394 |

395 |

396 |

Fold a function over the keys within a trie that share a common prefix.

397 | Traverses in alphabetical order.

398 | 399 |

foldl/3

400 |
401 |

foldl(F::fun((nonempty_string(), any(), any()) -> any()), A::any(), Node::trie()) -> any()

402 |

403 |

404 |

Fold a function over the trie.

405 | Traverses in alphabetical order.

406 | 407 |

foldl_similar/4

408 |
409 |

foldl_similar(Similar::nonempty_string(), F::fun((nonempty_string(), any(), any()) -> any()), A::any(), Node::trie()) -> any()

410 |

411 |

412 |

Fold a function over the keys within a trie that share a common prefix.

413 | Traverses in alphabetical order.

414 | 415 |

foldr/3

416 |
417 |

foldr(F::fun((nonempty_string(), any(), any()) -> any()), A::any(), Node::trie()) -> any()

418 |

419 |

420 |

Fold a function over the trie in reverse.

421 | Traverses in reverse alphabetical order.

422 | 423 |

foldr_similar/4

424 |
425 |

foldr_similar(Similar::nonempty_string(), F::fun((nonempty_string(), any(), any()) -> any()), A::any(), Node::trie()) -> any()

426 |

427 |

428 |

Fold a function over the keys within a trie that share a common prefix in reverse.

429 | Traverses in reverse alphabetical order.

430 | 431 |

foreach/2

432 |
433 |

foreach(F::fun((nonempty_string(), any()) -> any()), Node::trie()) -> any()

434 |

435 |

436 |

Call a function for each element.

437 | Traverses in alphabetical order.

438 | 439 |

from_list/1

440 |
441 |

from_list(L::[nonempty_string() | tuple()]) -> trie()

442 |

443 |

444 |

Create a trie from a list.

445 |

446 | 447 |

is_bytestring/1

448 |
449 |

is_bytestring(L::[byte()]) -> true | false

450 |

451 |

452 |

Test if the parameter is a byte string.

453 |

454 | 455 |

is_bytestring_nonempty/1

456 |
457 |

is_bytestring_nonempty(L::[byte(), ...]) -> true | false

458 |

459 |

460 |

Test if the parameter is a nonempty byte string.

461 |

462 | 463 |

is_key/2

464 |
465 |

is_key(T::nonempty_string(), Node::trie()) -> boolean()

466 |

467 |

468 |

Determine if a key exists in a trie.

469 |

470 | 471 |

is_pattern/1

472 |
473 |

is_pattern(Pattern::string()) -> true | false

474 |

475 |

476 |

Test to determine if a string is a pattern.

477 | "*" is the wildcard character (equivalent to the ".+" regex). 478 | "**" is forbidden.

479 | 480 |

is_pattern2/1

481 |
482 |

is_pattern2(Pattern::string()) -> true | false

483 |

484 |

485 |

Test to determine if a string is a pattern (using 2 wildcard characters).

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 |

is_pattern2_bytes/1

491 |
492 |

is_pattern2_bytes(Pattern::[byte()]) -> true | false

493 |

494 |

495 |

Test to determine if a byte string is a pattern (using 2 wildcard characters).

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 |

is_pattern_bytes/1

501 |
502 |

is_pattern_bytes(Pattern::[byte()]) -> true | false

503 |

504 |

505 |

Test to determine if a byte string is a pattern.

506 | "*" is the wildcard character (equivalent to the ".+" regex). 507 | "**" is forbidden.

508 | 509 |

is_prefix/2

510 |
511 |

is_prefix(T::string(), X2::trie()) -> true | false

512 |

513 |

514 |

Determine if the prefix provided has existed within a trie.

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 |

is_prefixed/2

521 |
522 |

is_prefixed(T::string(), X2::trie()) -> true | false

523 |

524 |

525 |

Determine if the provided string has a prefix within a trie.

526 |

527 | 528 |

is_prefixed/3

529 |
530 |

is_prefixed(Key::string(), Exclude::string(), Node::trie()) -> true | false

531 |

532 |

533 |

Determine if the provided string has an acceptable prefix within a trie.

534 | The prefix within the trie must match at least 1 character that is not 535 | within the excluded list of characters.

536 | 537 |

iter/2

538 |
539 |

iter(F::fun((string(), any(), fun(() -> any())) -> any()), Node::trie()) -> ok

540 |

541 |

542 |

Iterate over a trie.

543 | Traverses in alphabetical order.

544 | 545 |

itera/3

546 |
547 |

itera(F::fun((string(), any(), any(), fun((any()) -> any())) -> any()), A::any(), Node::trie()) -> any()

548 |

549 |

550 |

Iterate over a trie with an accumulator.

551 | Traverses in alphabetical order.

552 | 553 |

map/2

554 |
555 |

map(F::fun((nonempty_string(), any()) -> any()), Node::trie()) -> trie()

556 |

557 |

558 |

Map a function over a trie.

559 | Traverses in reverse alphabetical order.

560 | 561 |

merge/3

562 |
563 |

merge(F::fun((nonempty_string(), any(), any()) -> any()), Node1::trie(), Node2::trie()) -> trie()

564 |

565 |

566 |

Merge two trie instance.

567 | Update the second trie parameter with all of the elements 568 | found within the first trie parameter.

569 | 570 |

new/0

571 |
572 |

new() -> empty_trie()

573 |

574 |

575 |

Create a new trie instance.

576 |

577 | 578 |

new/1

579 |
580 |

new(L::[nonempty_string() | tuple()]) -> trie()

581 |

582 |

583 |

Create a new trie instance from a list.

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 |

pattern2_fill/2

591 |
592 |

pattern2_fill(FillPattern::string(), Parameters::[string()]) -> {ok, string()} | {error, parameters_ignored | parameter_missing}

593 |

594 |

595 |

Fill wildcard characters in a string.

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 |

pattern2_fill/4

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 |

Fill wildcard characters in a string.

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 |

pattern2_parse/2

611 |
612 |

pattern2_parse(Pattern::string(), L::string()) -> [string()] | error

613 |

614 |

615 |

Parse a string based on the supplied wildcard pattern (using 2 wildcard characters).

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 |

pattern2_parse/3

621 |
622 |

pattern2_parse(Pattern::string(), L::string(), Option::default | with_suffix | expanded) -> [string()] | {[string()], string()} | [string() | {exact, string()}] | error

623 |

624 |

625 |

Parse a string based on the supplied wildcard pattern (using 2 wildcard characters).

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 |

pattern2_suffix/2

631 |
632 |

pattern2_suffix(Pattern::string(), L::string()) -> string() | error

633 |

634 |

635 |

Parse a string based on the supplied wildcard pattern (using 2 wildcard characters) to return only the suffix after the pattern.

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 |

pattern_fill/2

641 |
642 |

pattern_fill(FillPattern::string(), Parameters::[string()]) -> {ok, string()} | {error, parameters_ignored | parameter_missing}

643 |

644 |

645 |

Fill wildcard characters in a string.

646 | The "*" wildcard character may be used consecutively by this function 647 | to have parameters concatenated.

648 | 649 |

pattern_fill/4

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 |

Fill wildcard characters in a string.

655 | The "*" wildcard character may be used consecutively by this function 656 | to have parameters concatenated.

657 | 658 |

pattern_parse/2

659 |
660 |

pattern_parse(Pattern::string(), L::string()) -> [string()] | error

661 |

662 |

663 |

Parse a string based on the supplied wildcard pattern.

664 | "*" is the wildcard character (equivalent to the ".+" regex). 665 | "**" is forbidden.

666 | 667 |

pattern_parse/3

668 |
669 |

pattern_parse(Pattern::string(), L::string(), Option::default | with_suffix | expanded) -> [string()] | {[string()], string()} | [string() | {exact, string()}] | error

670 |

671 |

672 |

Parse a string based on the supplied wildcard pattern.

673 | "*" is the wildcard character (equivalent to the ".+" regex). 674 | "**" is forbidden.

675 | 676 |

pattern_suffix/2

677 |
678 |

pattern_suffix(Pattern::string(), L::string()) -> string() | error

679 |

680 |

681 |

Parse a string based on the supplied wildcard pattern to return only the suffix after the pattern.

682 | "*" is the wildcard character (equivalent to the ".+" regex). 683 | "**" is forbidden.

684 | 685 |

prefix/3

686 |
687 |

prefix(Key::nonempty_string(), Value::any(), Node::trie()) -> nonempty_trie()

688 |

689 |

690 |

Insert a value as the first list element in a trie instance.

691 | The reverse of append/3.

692 | 693 |

size/1

694 |
695 |

size(Node::trie()) -> non_neg_integer()

696 |

697 |

698 |

Size of a trie instance.

699 |

700 | 701 |

store/2

702 |
703 |

store(Key::nonempty_string(), Node::trie()) -> nonempty_trie()

704 |

705 |

706 |

Store only a key in a trie instance.

707 |

708 | 709 |

store/3

710 |
711 |

store(Key::nonempty_string(), NewValue::any(), Node::trie()) -> nonempty_trie()

712 |

713 |

714 |

Store a key/value pair in a trie instance.

715 |

716 | 717 |

take/2

718 |
719 |

take(Key::nonempty_string(), Node::trie()) -> {any(), trie()} | error

720 |

721 |

722 |

Take a value from the trie.

723 |

724 | 725 |

to_list/1

726 |
727 |

to_list(Node::trie()) -> [{nonempty_string(), any()}]

728 |

729 |

730 |

Convert all entries in a trie to a list.

731 | The list is in alphabetical order.

732 | 733 |

to_list_similar/2

734 |
735 |

to_list_similar(Similar::nonempty_string(), Node::trie()) -> [{nonempty_string(), any()}]

736 |

737 |

738 |

Return a list of all entries within a trie that share a common prefix.

739 |

740 | 741 |

update/3

742 |
743 |

update(T::nonempty_string(), F::fun((any()) -> any()), Node::nonempty_trie()) -> nonempty_trie()

744 |

745 |

746 |

Update a value in a trie.

747 |

748 | 749 |

update/4

750 |
751 |

update(Key::nonempty_string(), F::fun((any()) -> any()), Initial::any(), Node::trie()) -> nonempty_trie()

752 |

753 |

754 |

Update or add a value in a trie.

755 |

756 | 757 |

update_counter/3

758 |
759 |

update_counter(Key::nonempty_string(), Increment::number(), Node::trie()) -> nonempty_trie()

760 |

761 |

762 |

Update a counter in a trie.

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 | --------------------------------------------------------------------------------