immutable-tree.ts 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. /**
  2. * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author David Sehnal <david.sehnal@gmail.com>
  5. */
  6. import { Map as ImmutableMap, OrderedSet } from 'immutable';
  7. /**
  8. * An immutable tree where each node requires a unique reference.
  9. * Represented as an immutable map.
  10. */
  11. export interface ImmutableTree<T> {
  12. readonly rootRef: ImmutableTree.Ref,
  13. readonly version: number,
  14. readonly nodes: ImmutableTree.Nodes<T>,
  15. getRef(e: T): ImmutableTree.Ref,
  16. getValue(ref: ImmutableTree.Ref): T | undefined
  17. }
  18. export namespace ImmutableTree {
  19. export type Ref = string
  20. export interface MutableNode<T> { ref: ImmutableTree.Ref, value: T, version: number, parent: ImmutableTree.Ref, children: OrderedSet<ImmutableTree.Ref> }
  21. export interface Node<T> extends Readonly<MutableNode<T>> { }
  22. export interface Nodes<T> extends ImmutableMap<ImmutableTree.Ref, Node<T>> { }
  23. class Impl<T> implements ImmutableTree<T> {
  24. readonly rootRef: ImmutableTree.Ref;
  25. readonly version: number;
  26. readonly nodes: ImmutableTree.Nodes<T>;
  27. readonly getRef: (e: T) => ImmutableTree.Ref;
  28. getValue(ref: Ref) {
  29. const n = this.nodes.get(ref);
  30. return n ? n.value : void 0;
  31. }
  32. constructor(rootRef: ImmutableTree.Ref, nodes: ImmutableTree.Nodes<T>, getRef: (e: T) => ImmutableTree.Ref, version: number) {
  33. this.rootRef = rootRef;
  34. this.nodes = nodes;
  35. this.getRef = getRef;
  36. this.version = version;
  37. }
  38. }
  39. /**
  40. * Create an instance of an immutable tree.
  41. */
  42. export function create<T>(root: T, getRef: (t: T) => ImmutableTree.Ref): ImmutableTree<T> {
  43. const ref = getRef(root);
  44. const r: Node<T> = { ref, value: root, version: 0, parent: ref, children: OrderedSet() };
  45. return new Impl(ref, ImmutableMap([[ref, r]]), getRef, 0);
  46. }
  47. export function asTransient<T>(tree: ImmutableTree<T>) {
  48. return new Transient(tree);
  49. }
  50. type N = Node<any>
  51. type Ns = Nodes<any>
  52. type VisitorCtx = { nodes: Ns, state: any, f: (node: N, nodes: Ns, state: any) => boolean | undefined | void };
  53. function _postOrderFunc(this: VisitorCtx, c: ImmutableTree.Ref | undefined) { _doPostOrder(this, this.nodes.get(c!)!); }
  54. function _doPostOrder(ctx: VisitorCtx, root: N) {
  55. if (root.children.size) {
  56. root.children.forEach(_postOrderFunc, ctx);
  57. }
  58. ctx.f(root, ctx.nodes, ctx.state);
  59. }
  60. /**
  61. * Visit all nodes in a subtree in "post order", meaning leafs get visited first.
  62. */
  63. export function doPostOrder<T, S>(tree: ImmutableTree<T>, root: Node<T>, state: S, f: (node: Node<T>, nodes: Nodes<T>, state: S) => boolean | undefined | void): S {
  64. const ctx: VisitorCtx = { nodes: tree.nodes, state, f };
  65. _doPostOrder(ctx, root);
  66. return ctx.state;
  67. }
  68. function _preOrderFunc(this: VisitorCtx, c: ImmutableTree.Ref | undefined) { _doPreOrder(this, this.nodes.get(c!)!); }
  69. function _doPreOrder(ctx: VisitorCtx, root: N) {
  70. const ret = ctx.f(root, ctx.nodes, ctx.state);
  71. if (typeof ret === 'boolean' && !ret) return;
  72. if (root.children.size) {
  73. root.children.forEach(_preOrderFunc, ctx);
  74. }
  75. }
  76. /**
  77. * Visit all nodes in a subtree in "pre order", meaning leafs get visited last.
  78. * If the visitor function returns false, the visiting for that branch is interrupted.
  79. */
  80. export function doPreOrder<T, S>(tree: ImmutableTree<T>, root: Node<T>, state: S, f: (node: Node<T>, nodes: Nodes<T>, state: S) => boolean | undefined | void): S {
  81. const ctx: VisitorCtx = { nodes: tree.nodes, state, f };
  82. _doPreOrder(ctx, root);
  83. return ctx.state;
  84. }
  85. function _subtree(n: N, nodes: Ns, subtree: N[]) { subtree.push(n); }
  86. /**
  87. * Get all nodes in a subtree, leafs come first.
  88. */
  89. export function subtreePostOrder<T>(tree: ImmutableTree<T>, root: Node<T>) {
  90. return doPostOrder<T, Node<T>[]>(tree, root, [], _subtree);
  91. }
  92. function _visitChildToJson(this: Ref[], ref: Ref) { this.push(ref); }
  93. interface ToJsonCtx { nodes: [any, any, any[]][], refs: string[], valueToJSON: (v: any) => any }
  94. function _visitNodeToJson(this: ToJsonCtx, node: Node<any>) {
  95. const children: Ref[] = [];
  96. node.children.forEach(_visitChildToJson as any, children);
  97. this.nodes.push([this.valueToJSON(node.value), node.parent, children]);
  98. this.refs.push(node.ref);
  99. }
  100. export interface Serialized {
  101. root: number, // root index
  102. nodes: [any /** value */, number /** parent index */, number[] /** children indices */][]
  103. }
  104. export function toJSON<T>(tree: ImmutableTree<T>, valueToJSON: (v: T) => any): Serialized {
  105. const ctx: ToJsonCtx = { nodes: [], refs: [], valueToJSON };
  106. tree.nodes.forEach(_visitNodeToJson as any, ctx);
  107. const map = new Map<string, number>();
  108. let i = 0;
  109. for (const n of ctx.refs) map.set(n, i++);
  110. for (const n of ctx.nodes) {
  111. n[1] = map.get(n[1]);
  112. const children = n[2];
  113. for (i = 0; i < children.length; i++) {
  114. children[i] = map.get(children[i]);
  115. }
  116. }
  117. return {
  118. root: map.get(tree.rootRef)!,
  119. nodes: ctx.nodes
  120. };
  121. }
  122. export function fromJSON<T>(data: Serialized, getRef: (v: T) => Ref, valueFromJSON: (v: any) => T): ImmutableTree<T> {
  123. const nodes = ImmutableMap<ImmutableTree.Ref, Node<T>>().asMutable();
  124. const values = data.nodes.map(n => valueFromJSON(n[0]));
  125. let i = 0;
  126. for (const value of values) {
  127. const node = data.nodes[i++];
  128. const ref = getRef(value);
  129. nodes.set(ref, {
  130. ref,
  131. value,
  132. version: 0,
  133. parent: getRef(values[node[1]]),
  134. children: OrderedSet(node[2].map(c => getRef(values[c])))
  135. });
  136. }
  137. return new Impl(getRef(values[data.root]), nodes.asImmutable(), getRef, 0);
  138. }
  139. function checkSetRef(oldRef: ImmutableTree.Ref, newRef: ImmutableTree.Ref) {
  140. if (oldRef !== newRef) {
  141. throw new Error(`Cannot setValue of node '${oldRef}' because the new value has a different ref '${newRef}'.`);
  142. }
  143. }
  144. function ensureNotPresent(nodes: Ns, ref: ImmutableTree.Ref) {
  145. if (nodes.has(ref)) {
  146. throw new Error(`Cannot add node '${ref}' because a different node with this ref already present in the tree.`);
  147. }
  148. }
  149. function ensurePresent(nodes: Ns, ref: ImmutableTree.Ref) {
  150. if (!nodes.has(ref)) {
  151. throw new Error(`Node '${ref}' is not present in the tree.`);
  152. }
  153. }
  154. function mutateNode(nodes: Ns, mutations: Map<ImmutableTree.Ref, N>, ref: ImmutableTree.Ref): N {
  155. ensurePresent(nodes, ref);
  156. if (mutations.has(ref)) {
  157. return mutations.get(ref)!;
  158. }
  159. const node = nodes.get(ref)!;
  160. const newNode: N = { ref: node.ref, value: node.value, version: node.version + 1, parent: node.parent, children: node.children.asMutable() };
  161. mutations.set(ref, newNode);
  162. nodes.set(ref, newNode);
  163. return newNode;
  164. }
  165. export class Transient<T> implements ImmutableTree<T> {
  166. nodes = this.tree.nodes.asMutable();
  167. version: number = this.tree.version + 1;
  168. private mutations: Map<ImmutableTree.Ref, Node<T>> = new Map();
  169. mutate(ref: ImmutableTree.Ref): MutableNode<T> {
  170. return mutateNode(this.nodes, this.mutations, ref);
  171. }
  172. get rootRef() { return this.tree.rootRef; }
  173. getRef(e: T) {
  174. return this.tree.getRef(e);
  175. }
  176. getValue(ref: Ref) {
  177. const n = this.nodes.get(ref);
  178. return n ? n.value : void 0;
  179. }
  180. add(parentRef: ImmutableTree.Ref, value: T) {
  181. const ref = this.getRef(value);
  182. ensureNotPresent(this.nodes, ref);
  183. const parent = this.mutate(parentRef);
  184. const node: Node<T> = { ref, version: 0, value, parent: parent.ref, children: OrderedSet<string>().asMutable() };
  185. this.mutations.set(ref, node);
  186. parent.children.add(ref);
  187. this.nodes.set(ref, node);
  188. return node;
  189. }
  190. setValue(ref: ImmutableTree.Ref, value: T): Node<T> {
  191. checkSetRef(ref, this.getRef(value));
  192. const node = this.mutate(ref);
  193. node.value = value;
  194. return node;
  195. }
  196. remove(ref: ImmutableTree.Ref): Node<T>[] {
  197. if (ref === this.rootRef) {
  198. return this.removeChildren(ref);
  199. }
  200. const { nodes, mutations } = this;
  201. const node = nodes.get(ref);
  202. if (!node) return [];
  203. const parent = nodes.get(node.parent)!;
  204. this.mutate(parent.ref).children.delete(ref);
  205. const st = subtreePostOrder(this, node);
  206. for (const n of st) {
  207. nodes.delete(n.ref);
  208. mutations.delete(n.ref);
  209. }
  210. return st;
  211. }
  212. removeChildren(ref: ImmutableTree.Ref): Node<T>[] {
  213. const { nodes, mutations } = this;
  214. let node = nodes.get(ref);
  215. if (!node || !node.children.size) return [];
  216. node = this.mutate(ref);
  217. const st = subtreePostOrder(this, node);
  218. // remove the last node which is the parent
  219. st.pop();
  220. node.children.clear();
  221. for (const n of st) {
  222. nodes.delete(n.ref);
  223. mutations.delete(n.ref);
  224. }
  225. return st;
  226. }
  227. asImmutable() {
  228. if (this.mutations.size === 0) return this.tree;
  229. this.mutations.forEach(m => (m as MutableNode<T>).children = m.children.asImmutable());
  230. return new Impl<T>(this.tree.rootRef, this.nodes.asImmutable(), this.tree.getRef, this.version);
  231. }
  232. constructor(private tree: ImmutableTree<T>) {
  233. }
  234. }
  235. }