LinkedList.ts 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  1. /*************************************************************
  2. *
  3. * Copyright (c) 2017-2022 The MathJax Consortium
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License");
  6. * you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. /**
  18. * @fileoverview Implement a generic LinkedList object.
  19. *
  20. * @author dpvc@mathjax.org (Davide Cervone)
  21. */
  22. /*****************************************************************/
  23. /**
  24. * A symbol used to mark the special node used to indicate
  25. * the start and end of the list.
  26. */
  27. export const END = Symbol();
  28. /**
  29. * Shorthand type for the functions used to sort the data items
  30. *
  31. * @template DataClass The type of data stored in the list
  32. */
  33. export type SortFn<DataClass> = (a: DataClass, b: DataClass) => boolean;
  34. /*****************************************************************/
  35. /**
  36. * The ListItem interface (for a specific type of data item)
  37. *
  38. * These are the items in the doubly-linked list.
  39. *
  40. * @template DataClass The type of data stored in the list
  41. */
  42. export class ListItem<DataClass> {
  43. /**
  44. * The data for the list item
  45. */
  46. public data: DataClass | symbol;
  47. /**
  48. * Pointers to the next item in the list
  49. */
  50. public next: ListItem<DataClass> = null;
  51. /**
  52. * Pointers to the previous item in the list
  53. */
  54. public prev: ListItem<DataClass> = null;
  55. /**
  56. * @param {any} data The data to be stored in the list item
  57. * @constructor
  58. */
  59. constructor(data: any = null) {
  60. this.data = data;
  61. }
  62. }
  63. /*****************************************************************/
  64. /**
  65. * Implements the generic LinkedList class
  66. *
  67. * @template DataClass The type of data stored in the list
  68. */
  69. export class LinkedList<DataClass> {
  70. /**
  71. * The linked list
  72. */
  73. protected list: ListItem<DataClass>;
  74. /**
  75. * This.list is a special ListItem whose next property
  76. * points to the head of the list and whose prev
  77. * property points to the tail. This lets us relink
  78. * the head and tail items in the same way as any other
  79. * item in the list, without having to handle special
  80. * cases.
  81. *
  82. * @param {DataClass[]} args The data items that form the initial list
  83. * @constructor
  84. */
  85. constructor(...args: DataClass[]) {
  86. this.list = new ListItem<DataClass>(END);
  87. this.list.next = this.list.prev = this.list;
  88. this.push(...args);
  89. }
  90. /**
  91. * Used for sorting and merging lists (Overridden by subclasses)
  92. *
  93. * @param {DataClass} a The first item to compare
  94. * @param {DataClass} b The second item to compare
  95. * @return {boolean} True if a is before b, false otherwise
  96. */
  97. public isBefore(a: DataClass, b: DataClass): boolean {
  98. return a < b;
  99. }
  100. /**
  101. * Push items on the end of the list
  102. *
  103. * @param {DataClass[]} args The list of data items to be pushed
  104. * @return {LinkedList} The LinkedList object (for chaining)
  105. */
  106. public push(...args: DataClass[]): LinkedList<DataClass> {
  107. for (const data of args) {
  108. let item = new ListItem<DataClass>(data);
  109. item.next = this.list;
  110. item.prev = this.list.prev;
  111. this.list.prev = item;
  112. item.prev.next = item;
  113. }
  114. return this;
  115. }
  116. /**
  117. * Pop the end item off the list and return its data
  118. *
  119. * @return {DataClass} The data from the last item in the list
  120. */
  121. public pop(): DataClass {
  122. let item = this.list.prev;
  123. if (item.data === END) {
  124. return null;
  125. }
  126. this.list.prev = item.prev;
  127. item.prev.next = this.list;
  128. item.next = item.prev = null;
  129. return item.data as DataClass;
  130. }
  131. /**
  132. * Push items at the head of the list
  133. *
  134. * @param {DataClass[]} args The list of data items to inserted
  135. * @return {LinkedList} The LinkedList object (for chaining)
  136. */
  137. public unshift(...args: DataClass[]): LinkedList<DataClass> {
  138. for (const data of args.slice(0).reverse()) {
  139. let item = new ListItem<DataClass>(data);
  140. item.next = this.list.next;
  141. item.prev = this.list;
  142. this.list.next = item;
  143. item.next.prev = item;
  144. }
  145. return this;
  146. }
  147. /**
  148. * Remove an item from the head of the list and return its data
  149. *
  150. * @return {DataClass} The data from the first item in the list
  151. */
  152. public shift(): DataClass {
  153. let item = this.list.next;
  154. if (item.data === END) {
  155. return null;
  156. }
  157. this.list.next = item.next;
  158. item.next.prev = this.list;
  159. item.next = item.prev = null;
  160. return item.data as DataClass;
  161. }
  162. /**
  163. * Remove items from the list
  164. *
  165. * @param {DataClass[]} items The items to remove
  166. */
  167. public remove(...items: DataClass[]) {
  168. const map = new Map<DataClass, boolean>();
  169. for (const item of items) {
  170. map.set(item, true);
  171. }
  172. let item = this.list.next;
  173. while (item.data !== END) {
  174. const next = item.next;
  175. if (map.has(item.data as DataClass)) {
  176. item.prev.next = item.next;
  177. item.next.prev = item.prev;
  178. item.next = item.prev = null;
  179. }
  180. item = next;
  181. }
  182. }
  183. /**
  184. * Empty the list
  185. *
  186. * @return {LinkedList} The LinkedList object (for chaining)
  187. */
  188. public clear(): LinkedList<DataClass> {
  189. this.list.next.prev = this.list.prev.next = null;
  190. this.list.next = this.list.prev = this.list;
  191. return this;
  192. }
  193. /**
  194. * An iterator for the list in forward order
  195. *
  196. * @yield {DataClass} The next item in the iteration sequence
  197. */
  198. public *[Symbol.iterator](): IterableIterator<DataClass> {
  199. let current = this.list.next;
  200. while (current.data !== END) {
  201. yield current.data as DataClass;
  202. current = current.next;
  203. }
  204. }
  205. /**
  206. * An iterator for the list in reverse order
  207. *
  208. * @yield {DataClass} The previous item in the iteration sequence
  209. */
  210. public *reversed(): IterableIterator<DataClass> {
  211. let current = this.list.prev;
  212. while (current.data !== END) {
  213. yield current.data as DataClass;
  214. current = current.prev;
  215. }
  216. }
  217. /**
  218. * Insert a new item into a sorted list in the correct locations
  219. *
  220. * @param {DataClass} data The data item to add
  221. * @param {SortFn} isBefore The function used to order the data
  222. * @param {LinkedList} The LinkedList object (for chaining)
  223. */
  224. public insert(data: DataClass, isBefore: SortFn<DataClass> = null) {
  225. if (isBefore === null) {
  226. isBefore = this.isBefore.bind(this);
  227. }
  228. let item = new ListItem<DataClass>(data);
  229. let cur = this.list.next;
  230. while (cur.data !== END && isBefore(cur.data as DataClass, item.data as DataClass)) {
  231. cur = cur.next;
  232. }
  233. item.prev = cur.prev;
  234. item.next = cur;
  235. cur.prev.next = cur.prev = item;
  236. return this;
  237. }
  238. /**
  239. * Sort the list using an optional sort function
  240. *
  241. * @param {SortFn} isBefore The function used to order the data
  242. * @return {LinkedList} The LinkedList object (for chaining)
  243. */
  244. public sort(isBefore: SortFn<DataClass> = null): LinkedList<DataClass> {
  245. if (isBefore === null) {
  246. isBefore = this.isBefore.bind(this);
  247. }
  248. //
  249. // Make an array of singleton lists
  250. //
  251. let lists: LinkedList<DataClass>[] = [];
  252. for (const item of this) {
  253. lists.push(new LinkedList<DataClass>(item as DataClass));
  254. }
  255. //
  256. // Clear current list
  257. //
  258. this.list.next = this.list.prev = this.list;
  259. //
  260. // Merge pairs of lists until there is only one left
  261. //
  262. while (lists.length > 1) {
  263. let l1 = lists.shift();
  264. let l2 = lists.shift();
  265. l1.merge(l2, isBefore);
  266. lists.push(l1);
  267. }
  268. //
  269. // Use the final list as our list
  270. //
  271. if (lists.length) {
  272. this.list = lists[0].list;
  273. }
  274. return this;
  275. }
  276. /**
  277. * Merge a sorted list with another sorted list
  278. *
  279. * @param {LinkedList} list The list to merge into this instance's list
  280. * @param {SortFn} isBefore The function used to order the data
  281. * @return {LinkedList} The LinkedList instance (for chaining)
  282. */
  283. public merge(list: LinkedList<DataClass>, isBefore: SortFn<DataClass> = null): LinkedList<DataClass> {
  284. if (isBefore === null) {
  285. isBefore = this.isBefore.bind(this);
  286. }
  287. //
  288. // Get the head of each list
  289. //
  290. let lcur = this.list.next;
  291. let mcur = list.list.next;
  292. //
  293. // While there is more in both lists
  294. //
  295. while (lcur.data !== END && mcur.data !== END) {
  296. //
  297. // If the merge item is before the list item
  298. // (we have found where the head of the merge list belongs)
  299. // Link the merge list into the main list at this point
  300. // and make the merge list be the remainder of the original list.
  301. // The merge continues by looking for where the rest of the original
  302. // list fits into the newly formed main list (the old merge list).
  303. // Otherwise
  304. // Go on to the next item in the main list
  305. //
  306. if (isBefore(mcur.data as DataClass, lcur.data as DataClass)) {
  307. [mcur.prev.next, lcur.prev.next] = [lcur, mcur];
  308. [mcur.prev, lcur.prev] = [lcur.prev, mcur.prev];
  309. [this.list.prev.next, list.list.prev.next] = [list.list, this.list];
  310. [this.list.prev, list.list.prev] = [list.list.prev, this.list.prev];
  311. [lcur, mcur] = [mcur.next, lcur];
  312. } else {
  313. lcur = lcur.next;
  314. }
  315. }
  316. //
  317. // If there is more to be merged (i.e., we came to the end of the main list),
  318. // then link that at the end of the main list.
  319. //
  320. if (mcur.data !== END) {
  321. this.list.prev.next = list.list.next;
  322. list.list.next.prev = this.list.prev;
  323. list.list.prev.next = this.list;
  324. this.list.prev = list.list.prev;
  325. list.list.next = list.list.prev = list.list;
  326. }
  327. return this;
  328. }
  329. }