UNPKG

@loken/hierarchies

Version:

Library for working with hierarchies of identifiers and identifiable objects.

198 lines (197 loc) 8.21 kB
import { MultiMap, LinearQueue, someToIterable, ProbabilityScale, randomInt } from "@loken/utilities"; import { Hierarchy } from "../hierarchies/hierarchy.js"; class ChildMap { /** Block instantiation by making the ctor private to simulate a static class. */ constructor() { } /** Create a child-map from an `IdSpec`. */ static fromIds(spec) { if (spec instanceof Map) return spec; if (Array.isArray(spec)) return ChildMap.fromRelations(spec); if (spec instanceof Hierarchy) return ChildMap.fromHierarchy(spec); throw new Error("Unsupported 'relations' specification."); } /** * Create a child-map from the nested property keys of the `source`. * * @param source The object describing the relations. * @param include Optional predicate used for determining whether a property should be included as an ID. */ static fromPropertyIds(source, include) { const childMap = new MultiMap(); let roots = Object.entries(source).map(([id, value]) => [id, value]); if (include && (roots = roots.filter(([id, value]) => include(id, value))), roots.length === 0) return childMap; for (const [id] of roots) childMap.getOrAdd(id); const store = new LinearQueue(); for (store.attach(roots); store.count > 0; ) { const [id, value] = store.detach(); if (typeof value != "object" || value === null) continue; let children = Object.entries(value); include && (children = children.filter(([childId, childValue]) => include(childId, childValue))), children.length !== 0 && (childMap.add(id, children.map(([childId]) => childId)), store.attach(children)); } return childMap; } /** Create a child-map from `ItemIdOptions`. */ static fromItems(options) { if (options.spec) return ChildMap.fromIds(options.spec); if (options.children) return ChildMap.fromChildren(options.items, options.identify, options.children); if (options.childIds) return ChildMap.fromChildIds(options.items, options.identify, options.childIds); if (options.parent) return ChildMap.fromParents(options.items, options.identify, options.parent); if (options.parentId) return ChildMap.fromParentIds(options.items, options.identify, options.parentId); throw new Error("Unsupported specification object!"); } /** Create a child-map from `items` using `identify` and `getChildren` delegates. */ static fromChildren(items, identify, getChildren) { var _a; const childMap = new MultiMap(); for (const item of someToIterable(items)) { const childIds = (_a = getChildren(item)) == null ? void 0 : _a.map(identify); childIds != null && childIds.length && childMap.add(identify(item), childIds); } return childMap; } /** Create a child-map from `items` using `identify` and `identifyChildren` delegates. */ static fromChildIds(items, identify, identifyChildren) { const childMap = new MultiMap(); for (const item of someToIterable(items)) { const childIds = identifyChildren(item); childIds != null && childIds.length && childMap.add(identify(item), childIds); } return childMap; } /** Create a child-map from `items` using `identify` and `getParent` delegates. */ static fromParents(items, identify, getParent) { const childMap = new MultiMap(); for (const item of someToIterable(items)) { const parent = getParent(item); parent ? childMap.add(identify(parent), identify(item)) : childMap.getOrAdd(identify(item)); } return childMap; } /** Create a child-map from `items` using `identify` and `identifyParent` delegates. */ static fromParentIds(items, identify, identifyParent) { const childMap = new MultiMap(); for (const item of someToIterable(items)) { const parentId = identifyParent(item); parentId ? childMap.add(parentId, identify(item)) : childMap.getOrAdd(identify(item)); } return childMap; } /** Create a child map from the `hierarchy`. */ static fromHierarchy(hierarchy) { return hierarchy.toChildMap(); } /** Create a child map from the `relations`. */ static fromRelations(relations) { const map = new MultiMap(); for (const [parent, child] of someToIterable(relations)) map.add(parent, child); return map; } /** Create relations from the `childMap`. */ static toRelations(childMap) { const relations = []; for (const [parent, children] of childMap) for (const child of children) relations.push([parent, child]); return relations; } /** Create a parent map from the `childMap`. */ static toParentMap(childMap, roots) { const parentMap = /* @__PURE__ */ new Map(); roots ?? (roots = this.getRoots(childMap)); for (const root of roots) childMap.get(root).size === 0 && parentMap.set(root, void 0); for (const [parent, children] of childMap) for (const child of children) parentMap.set(child, parent); return parentMap; } /** Create a descendants map from the `childMap`. */ static toDescendantMap(childMap, parentMap) { parentMap ?? (parentMap = this.toParentMap(childMap)); const descendantMap = new MultiMap(); for (const [child, parent] of parentMap) { if (parent === void 0) { descendantMap.getOrAdd(child); continue; } let ancestor = parent; for (; ancestor !== void 0; ) descendantMap.getOrAdd(ancestor).add(child), ancestor = parentMap.get(ancestor); } return descendantMap; } /** Create an ancestor map from the `childMap`. */ static toAncestorMap(childMap, parentMap) { parentMap ?? (parentMap = this.toParentMap(childMap)); const ancestorMap = new MultiMap(); for (const [child, parent] of parentMap) { const ancestors = ancestorMap.getOrAdd(child); let ancestor = parent; for (; ancestor !== void 0; ) ancestors.add(ancestor), ancestor = parentMap.get(ancestor); } return ancestorMap; } /** Get the set of IDs representing the roots of the `childMap`. */ static getRoots(childMap) { const seenChildren = /* @__PURE__ */ new Set(), roots = /* @__PURE__ */ new Set(); for (const [parent, children] of childMap) { seenChildren.has(parent) || roots.add(parent); for (const child of children) seenChildren.add(child), roots.delete(child); } return roots; } /** * Add the `ancestors` to the `childMap`. * @param ancestors The ancestor IDs organized from a node and up through its parents. * @param childMap A child-map. Default: Empty `MultiMap`. * @returns The `childMap`. */ static addAncestors(ancestors, childMap = new MultiMap()) { if (ancestors.length !== 0) return ancestors.length === 1 ? childMap.getOrAdd(ancestors[0]) : ancestors.reduce((child, parent) => (childMap.add(parent, child), parent)), childMap; } /** * Generate a child map for `count` IDs using a `create` function and randomly created structure. * @param count The number of IDs to create. * @param create The delegate responsible for creating new IDs. * @param chance Fraction chance to add move a layer deeper in the tree. (Default: 0.50) * @param decay The decay to apply to the `chance` for each layer. */ static generate(options) { const { count, create } = options, childMap = new MultiMap(); for (let index = 0; index < count; index++) { const probability = new ProbabilityScale({ mode: "decay", probability: options.chance ?? 0.5, scale: options.decay ?? 0.1 }), ancestry = []; let siblings = childMap.keys().toArray(); for (; siblings.length && probability.sample(); ) { const branch = siblings[siblings.length === 1 ? 0 : randomInt(0, siblings.length)], children = childMap.get(branch); siblings = children ? [...children] : [], ancestry.push(branch), probability.increment(); } const id = create({ index, siblings, ancestry }); ancestry.length === 0 ? childMap.getOrAdd(id) : childMap.add(ancestry.at(-1), id); } return childMap; } } export { ChildMap }; //# sourceMappingURL=child-map.js.map