@loken/hierarchies
Version:
Library for working with hierarchies of identifiers and identifiable objects.
198 lines (197 loc) • 8.21 kB
JavaScript
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