Guest User

Untitled

a guest
May 27th, 2018
90
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 12.43 KB | None | 0 0
  1. /*
  2. * The MIT License
  3. *
  4. * Copyright (c) 2010 Mike de Boer
  5. *
  6. * Permission is hereby granted, free of charge, to any person obtaining a copy
  7. * of this software and associated documentation files (the "Software"), to deal
  8. * in the Software without restriction, including without limitation the rights
  9. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  10. * copies of the Software, and to permit persons to whom the Software is
  11. * furnished to do so, subject to the following conditions:
  12. *
  13. * The above copyright notice and this permission notice shall be included in
  14. * all copies or substantial portions of the Software.
  15. *
  16. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  17. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  18. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  19. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  20. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  21. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  22. * THE SOFTWARE.
  23. */
  24.  
  25. // Translated to Typescript
  26.  
  27. class Trie {
  28. constructor(
  29. private stem: string = "",
  30. private sorting = Trie.SORT_DESC) {
  31. }
  32. public nstem = this.stem.charCodeAt(0);
  33. private wordCount = 0;
  34. private prefixCount = 0;
  35. public children: Trie[] = [];
  36. private meta = [];
  37.  
  38. private static SORT_ASC = 0x0001;
  39. private static SORT_DESC = 0x0002;
  40. private static SORT_NONE = 0x0004;
  41.  
  42. /**
  43. * Add a word to the existing dictionary. If a trie node doesn't exist
  44. * yet, it is created with that character as its stem.
  45. * Since an add is already an expensive action, compared to adding nodes to
  46. * native Javascript containers like Array or Object, inserting a trie
  47. * node in lexical order is relatively cheap.
  48. * Please refer to the test suite to compare performance in your browser(s).
  49. *
  50. * @param {String} word Remainder of the word that is added to the root trie
  51. * @param {Object} meta Metadata associated with a word
  52. * @return {void}
  53. */
  54. add(word: string, meta: { word?: string }): void {
  55. if (word) {
  56. let t;
  57. const s = this.sorting;
  58. let i = 0;
  59. const k = word.charAt(0);
  60. const c = this.children;
  61. let l = c.length;
  62. meta = meta || {};
  63. if (!meta.word)
  64. meta.word = word;
  65. // check if a child with stem 'k' already exists:
  66. for (; i < l; ++i) {
  67. if (c[i].stem == k) {
  68. t = c[i];
  69. break;
  70. }
  71. }
  72. if (!t) {
  73. ++this.prefixCount;
  74. t = new Trie(k, s);
  75. if (!s || !c.length || s & Trie.SORT_NONE) {
  76. c.push(t);
  77. }
  78. else if (s & Trie.SORT_DESC) {
  79. i = l;
  80. do {
  81. if (--i < 0) {
  82. c.unshift(t);
  83. break;
  84. }
  85. } while (c[i].stem > k)
  86. if (i >= 0)
  87. c.splice(i + 1, 0, t);
  88. }
  89. else {
  90. i = 0, --l;
  91. do {
  92. if (++i > l) {
  93. c.unshift(t);
  94. break;
  95. }
  96. } while (c[i].stem > k)
  97. if (i <= l)
  98. c.splice(i, 0, t);
  99. }
  100. }
  101. t.add(word.substring(1), meta);
  102. }
  103. else {
  104. this.meta.push(meta);
  105. ++this.wordCount;
  106. }
  107. }
  108.  
  109. /**
  110. * Update a word in the dictionary. This update implementation is
  111. * implemented like a file rename action as on a filesystem: add a node
  112. * with the new name and remove the outdated, 'old' version.
  113. *
  114. * @param {String} sOld the old word to be replaced by the word provided
  115. * by 'sNew'
  116. * @param {String} sNew the new word to be added to the dictionary
  117. * @param {Object} meta Metadata associated with a word
  118. * @return {void}
  119. */
  120. update(sOld: string, sNew: string, meta: object): void {
  121. this.remove(sOld);
  122. this.add(sNew, meta);
  123. }
  124.  
  125. /**
  126. * Remove a word from the dictionary. This function uses the
  127. * walker, which is a generic implementation of a tree walker.
  128. *
  129. * @param {String} word the word to remove
  130. * @return {void}
  131. */
  132. remove(word: string): void {
  133. walker(word, this, (trie, idx) => {
  134. trie.children.splice(idx, 1);
  135. });
  136. }
  137.  
  138. /**
  139. * Find a trie node that is paired with a word or prefix 's'. Like the
  140. * {@link remove} function, this function also uses the walker.
  141. *
  142. * @param {string} prefix the word or prefix to search for in the dictionary
  143. * @return {Trie}
  144. */
  145. find(prefix: string): Trie {
  146. return walker(prefix, this, (trie, idx) => trie.children[idx]);
  147. }
  148.  
  149. /**
  150. * @alias {find}
  151. *
  152. * @param {String} prefix the word or prefix to search for in the dictionary
  153. * @return {Trie}
  154. */
  155. findPrefix(prefix: string): Trie {
  156. // AFAIK, this is just an alias of find, because that returns a trie rootnode.
  157. // From that rootnode, it's easy to create a list of disambiguations.
  158. return this.find(prefix);
  159. }
  160.  
  161. /**
  162. * Retrieve a direct child node of this dictionary with 'prefix'.
  163. *
  164. * @param {String} prefix s the word or prefix to search for in the
  165. * children of this dictionary
  166. * @return {Trie}
  167. */
  168. getChild(prefix: string): Trie {
  169. let i = 0;
  170. const c = this.children;
  171. const l = c.length;
  172. for (; i < l; ++i) {
  173. if (c[i].stem == prefix)
  174. return c[i];
  175. }
  176.  
  177. return null;
  178. }
  179.  
  180. /**
  181. * A version of {@link getChild} with a Boolean return type.
  182. *
  183. * @param {String} prefix s the word or prefix to search for in the
  184. * children of this dictionary
  185. * @return {Boolean}
  186. */
  187. hasChild(prefix: string): boolean {
  188. return this.getChild(prefix) !== null;
  189. }
  190.  
  191. /**
  192. * Resort this dictionary in lexical order, either in an ascending or
  193. * descending direction.
  194. * Since it uses the native {@link Array#sort} method, sorting speed can
  195. * be considered linear O(n) to the size of the trie, i.e. the word count.
  196. * Please refer to the test suite to compare performance in your browser(s).
  197. *
  198. * @param {Number} direction sorting direction. Possible values:
  199. * {@link Trie#SORT_ASC}
  200. * {@link Trie#SORT_DESC}
  201. * @return {void}
  202. */
  203. sort(direction: number): void {
  204. if (typeof direction == "undefined")
  205. direction = Trie.SORT_DESC;
  206. if (!this.prefixCount || this.sorting === direction) return;
  207. this.sorting = direction;
  208. if (direction & Trie.SORT_NONE) return;
  209. const c = this.children;
  210. let i = c.length - 1;
  211. const m = direction & Trie.SORT_ASC ? sortAsc : sortDesc;
  212. c.sort(m);
  213. for (; i >= 0; --i)
  214. c[i].sort(direction);
  215. }
  216.  
  217. /**
  218. * Retrieve the Array of words that originate from this trie.
  219. * The main use-case for this function is for implementations of the
  220. * type-ahead user experience pattern, but can be used to other ends as
  221. * well, of course.
  222. * The performance of this function still needs to be profiled against
  223. * alternatives, like pre-caching the words Array per Trie when it's
  224. * instantiated.
  225. *
  226. * @return {Array}
  227. */
  228. getWords(): Array<any> {
  229. let words = [];
  230. const c = this.children;
  231. let i = 0;
  232. const l = c.length;
  233. for (; i < l; ++i) {
  234. if (c[i].wordCount) {
  235. words = words.concat(c[i].meta.map(meta => meta.word));
  236. }
  237. words = words.concat(c[i].getWords());
  238. }
  239. return words;
  240. }
  241.  
  242. /**
  243. * Retrieve the prefix count of the applied argument
  244. *
  245. * @param {String} word the prefix or word-completing stem
  246. * @return {Number}
  247. */
  248. getPrefixCount(word: string): number {
  249. return walker(word, this, (trie, idx) => trie.children[idx].prefixCount) || 0;
  250. }
  251.  
  252. /**
  253. * Retrieve the word count of the applied argument
  254. *
  255. * @param {String} word the prefix or word-completing stem
  256. * @return {Number}
  257. */
  258. getWordCount(word: string): number {
  259. return walker(word, this, (trie, idx) => trie.children[idx].wordCount) || 0;
  260. }
  261.  
  262. /**
  263. * Overrides Object.prototype.toString to deliver a more context sensitive
  264. * String representation of a Trie.
  265. *
  266. * @return {String}
  267. */
  268. toString(): string {
  269. return `[Trie] '${this.stem}': {\n stem: ${this.stem},\n prefixCount: ${this.prefixCount},\n wordCount: ${this.wordCount},\n metadata: ${JSON.stringify(this.meta)},\n children: [Array]{${this.children.length}}\n}`;
  270. }
  271.  
  272. /**
  273. * Load this Trie instance with properties from `json`; a serialized old(er)
  274. * version.
  275. *
  276. * @param {Object} json A serialized version of a Trie
  277. * @return {void}
  278. */
  279. fromJSON(json: any): void {
  280. STATIC_PROPS.forEach(prop => {
  281. this[prop] = json[prop];
  282. });
  283. this.children = json.children.map(data => {
  284. const child = new Trie();
  285. child.fromJSON(data);
  286. return child;
  287. });
  288. }
  289.  
  290. /**
  291. * Serialize this Trie instance to a JSON blob that may be stringified
  292. * and used at convenience.
  293. *
  294. * @return {Object}
  295. */
  296. toJSON(): object {
  297. const json = {
  298. children: this.children.map(child => child.toJSON())
  299. };
  300. STATIC_PROPS.forEach(prop => {
  301. json[prop] = this[prop];
  302. });
  303. return json;
  304. }
  305. }
  306.  
  307. var STATIC_PROPS = ["stem", "nstem", "sorting", "wordCount", "prefixCount", "meta"];
  308.  
  309. /**
  310. * NOT named after Johnny, but merely after the verb 'to walk'.
  311. * This function walks along a Trie top-down until it finds the node which
  312. * fully represents the term/ prefix/ word that was searched for.
  313. * It passes the parent node of the found Trie and its index to a callback
  314. * function. It passes the parent node, because otherwise Trie mutation would
  315. * become increasingly more difficult.
  316. *
  317. * An earlier implementation of this function used a naive recursive algorithm,
  318. * but my friend - @ejpbruel - has shown me that you can simply rewrite any form
  319. * of tail-recursion to an inner loop.
  320. *
  321. * @param {String} word the word or prefix to search for
  322. * @param {Trie} trie the root trie node to walk through
  323. * @param {Function} method callback function to which the results of the
  324. * walker are passed
  325. * @return {mixed}
  326. * @memberOf Trie
  327. */
  328. function walker(word: string, trie: Trie, method: Function) {
  329. if (!word || !trie || !method) return null;
  330. let ch;
  331. let c;
  332. let l;
  333. let i;
  334. let prev;
  335.  
  336. while (word.length > 0) {
  337. ch = word.charAt(0),
  338. c = trie.children,
  339. l = c.length,
  340. i = 0;
  341. for (; i < l; ++i) {
  342. if (ch == c[i].stem)
  343. break;
  344. }
  345. if (i == l)
  346. return null; // not found
  347. word = word.substring(1),
  348. prev = trie,
  349. trie = c[i];
  350. }
  351. return method(prev, i);
  352. }
  353.  
  354. /**
  355. * Sorting helper function that can be passed to Array.sort().
  356. * The result of this helper will be that all nodes will be sorted in
  357. * ascending lexical order.
  358. *
  359. * @param {Trie} a first element for comparison
  360. * @param {Trie} b second element for comparison
  361. * @return {Number}
  362. * @memberOf Trie
  363. */
  364. function sortAsc(a: Trie, b: Trie): number {
  365. const s1 = a.nstem;
  366. const s2 = b.nstem;
  367. return (s1 < s2) ? 1 : (s1 > s2) ? -1 : 0;
  368. }
  369.  
  370. /**
  371. * Sorting helper function that can be passed to Array.sort().
  372. * The result of this helper will be that all nodes will be sorted in
  373. * descending lexical order.
  374. *
  375. * @param {Trie} a first element for comparison
  376. * @param {Trie} b second element for comparison
  377. * @return {Number}
  378. * @memberOf Trie
  379. */
  380. function sortDesc(a: Trie, b: Trie): number {
  381. const s1 = a.nstem;
  382. const s2 = b.nstem;
  383. return (s1 > s2) ? 1 : (s1 < s2) ? -1 : 0;
  384. }
Add Comment
Please, Sign In to add comment