LevelledTopologicalSorter.ts 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. import { injectable } from 'inversify';
  2. import { ILevelledTopologicalSorter } from '../interfaces/utils/ILevelledTopologicalSorter';
  3. type TVisitMark = 'ok' | 'visiting';
  4. interface IVisitMarks <TValue extends string> {
  5. [key: string]: TVisitMark;
  6. }
  7. /**
  8. * Port and rework of https://github.com/loveencounterflow/ltsort
  9. */
  10. @injectable()
  11. export class LevelledTopologicalSorter <TValue extends string = string> implements ILevelledTopologicalSorter<TValue> {
  12. /**
  13. * @type {Map<TValue, TValue[]}
  14. */
  15. private readonly graph: Map<TValue, TValue[]> = new Map();
  16. /**
  17. * @param {TValue} precedent
  18. * @param {TValue | null} consequent
  19. * @returns {this}
  20. */
  21. public add (
  22. precedent: TValue,
  23. consequent: TValue | null = null
  24. ): this {
  25. if (consequent !== null) {
  26. return this.link(precedent, consequent);
  27. }
  28. return this.register(precedent);
  29. }
  30. /**
  31. * As given in http://en.wikipedia.org/wiki/Topological_sorting
  32. *
  33. * @returns {TValue[]}
  34. */
  35. public sort (): TValue[] {
  36. const consequents: TValue[] = Array.from(this.graph.keys());
  37. const results: TValue[] = [];
  38. const marks: IVisitMarks<TValue> = {};
  39. for (const consequent of consequents) {
  40. if (marks[consequent] !== undefined) {
  41. continue;
  42. }
  43. this.visit(results, marks, consequent);
  44. }
  45. return results;
  46. }
  47. /**
  48. * @returns {TValue[][]}
  49. */
  50. public sortByGroups (): TValue[][] {
  51. this.sort();
  52. const resultItemsGroups: TValue[][] = [];
  53. while (this.hasNodes()) {
  54. const rootNodes: TValue[] = this.findRootNodes();
  55. resultItemsGroups.push(rootNodes);
  56. for (const rootNode of rootNodes) {
  57. this.delete(rootNode);
  58. }
  59. }
  60. return resultItemsGroups;
  61. }
  62. /**
  63. * @param {TValue} consequent
  64. */
  65. private delete (consequent: TValue): void {
  66. const precedents: TValue[] = this.getPrecedents(consequent);
  67. if (precedents.length) {
  68. throw new Error(`Unable to remove non-root node: ${consequent}`);
  69. }
  70. this.graph.delete(consequent);
  71. const precedentsGroups: string[][] = Array.from(this.graph.values());
  72. for (const precedentsGroup of precedentsGroups) {
  73. const precedentsCount: number = precedentsGroup.length - 1;
  74. for (let index: number = precedentsCount; index >= 0; index = index - 1) {
  75. if (precedentsGroup[index] !== consequent) {
  76. continue;
  77. }
  78. precedentsGroup.splice(index, 1);
  79. }
  80. }
  81. }
  82. /**
  83. * @returns {TValue[]}
  84. */
  85. private findRootNodes (): TValue[] {
  86. const consequents: TValue[] = Array.from(this.graph.keys());
  87. const rootNodes: TValue[] = [];
  88. for (const consequent of consequents) {
  89. if (!this.hasPrecedents(consequent)) {
  90. rootNodes.push(consequent);
  91. }
  92. }
  93. return rootNodes;
  94. }
  95. /**
  96. * @param {TValue} consequent
  97. * @returns {TValue[]}
  98. */
  99. private getPrecedents (consequent: TValue): TValue[] {
  100. const precedents: TValue[] | undefined = this.graph.get(consequent);
  101. if (!precedents) {
  102. throw new Error(`Unknown node: ${consequent}`);
  103. }
  104. return precedents;
  105. }
  106. /**
  107. * @returns {boolean}
  108. */
  109. private hasNodes (): boolean {
  110. return this.graph.size > 0;
  111. }
  112. /**
  113. * @param {TValue} consequent
  114. * @returns {boolean}
  115. */
  116. private hasPrecedents (consequent: TValue): boolean {
  117. return this.getPrecedents(consequent).length > 0;
  118. }
  119. /**
  120. * @param {TValue} precedent
  121. * @param {TValue} consequent
  122. * @returns {this}
  123. */
  124. private link (precedent: TValue, consequent: TValue): this {
  125. this.register(precedent);
  126. this.register(consequent);
  127. const target: TValue[] | undefined = this.graph.get(consequent);
  128. if (target && !target.includes(precedent)) {
  129. target.push(precedent);
  130. }
  131. return this;
  132. }
  133. /**
  134. * @param {TValue} name
  135. * @returns {this}
  136. */
  137. private register (name: TValue): this {
  138. if (!this.graph.has(name)) {
  139. this.graph.set(name, []);
  140. }
  141. return this;
  142. }
  143. /**
  144. * @param {TValue[]} results
  145. * @param {IVisitMarks<TValue>} marks
  146. * @param {TValue} name
  147. * @returns {null}
  148. */
  149. private visit (
  150. results: TValue[],
  151. marks: IVisitMarks<TValue>,
  152. name: TValue
  153. ): void {
  154. const mark: TVisitMark = marks[name];
  155. if (mark === 'visiting') {
  156. throw new Error(`Detected cycle involving node: ${name}`);
  157. }
  158. if (mark) {
  159. return;
  160. }
  161. marks[name] = 'visiting';
  162. const precedents: TValue[] = this.getPrecedents(name);
  163. for (const precedent of precedents) {
  164. this.visit(results, marks, precedent);
  165. }
  166. marks[name] = 'ok';
  167. results.push(name);
  168. return;
  169. }
  170. }