Skip to content

Commit

Permalink
Added methods for finding or checking existence of a matching ancesto…
Browse files Browse the repository at this point in the history
…r or descendant.
  • Loading branch information
gautelo committed Feb 2, 2024
1 parent a4ca520 commit dc426bc
Show file tree
Hide file tree
Showing 3 changed files with 141 additions and 1 deletion.
58 changes: 58 additions & 0 deletions src/hierarchies/hierarchy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,54 @@ export class Hierarchy<Item, Id = Item> {
}
}


/**
* Find a node matching the `search` which is an ancestor of a node with one of the `ids`.
*/
public findAncestor(ids: Multiple<Id>, search: Id | Id[] | NodePredicate<Item>, includeSelf = false) {
const roots = this.get(...spreadMultiple(ids));

return Nodes.findAncestor(roots, this.normalizeSearch(search), includeSelf);
}

/**
* Find a node matching the `search` which is an descendant of a node with one of the `ids`.
*/
public findDescendant(ids: Multiple<Id>, search: Id | Id[]| NodePredicate<Item>, includeSelf = false) {
const roots = this.get(...spreadMultiple(ids));

return Nodes.findDescendant(roots, this.normalizeSearch(search), includeSelf);
}

/**
* Find nodes matching the `search` which are descendants of a node with one of the `ids`.
*/
public findDescendants(ids: Multiple<Id>, search: Id | Id[] | NodePredicate<Item>, includeSelf = false) {
const roots = this.get(...spreadMultiple(ids));

return Nodes.findDescendants(roots, this.normalizeSearch(search), includeSelf);
}


/**
* Does a node with one of the `ids` have an ancestor node matching the `search`?
*/
public hasAncestor(ids: Multiple<Id>, search: Id | Id[] | NodePredicate<Item>, includeSelf = false) {
const roots = this.get(...spreadMultiple(ids));

return Nodes.hasAncestor(roots, this.normalizeSearch(search), includeSelf);
}

/**
* Does a node with one of the `ids` have a descendant node matching the `search`?
*/
public hasDescendant(ids: Multiple<Id>, search: Id | Id[] | NodePredicate<Item>, includeSelf = false) {
const roots = this.get(...spreadMultiple(ids));

return Nodes.hasDescendant(roots, this.normalizeSearch(search), includeSelf);
}


/**
* Create a new `Hierarchy` from matching items.
*
Expand Down Expand Up @@ -467,6 +515,16 @@ export class Hierarchy<Item, Id = Item> {
spec: childMap,
});
}


protected normalizeSearch(search: Id | Id[] | NodePredicate<Item>): NodePredicate<Item> {
if (typeof search === 'function')
return search as NodePredicate<Item>;
if (Array.isArray(search))
return (node) => search.includes(this.#identify(node.item));
else
return (node) => this.#identify(node.item) === search;
}
//#endregion

//#region MultiMaps
Expand Down
42 changes: 41 additions & 1 deletion src/nodes/node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { type Multiple, spreadMultiple } from '@loken/utilities';
import { traverseGraph } from '../traversal/traverse-graph.js';
import { traverseSequence } from '../traversal/traverse-sequence.js';
import type { TraversalType } from '../traversal/traverse-types.js';
import type { DeBrand } from './node.types.js';
import type { DeBrand, NodePredicate } from './node.types.js';
import { nodesToItems } from './node-conversion.js';


Expand Down Expand Up @@ -231,6 +231,46 @@ export class HCNode<Item> {
public getDescendantItems(includeSelf = false, type: TraversalType = 'breadth-first') {
return nodesToItems(this.traverseDescendants(includeSelf, type));
}


/** Find the first ancestor node matching the `search`. */
public findAncestor(search: NodePredicate<Item>, includeSelf = false) {
for (const ancestor of this.traverseAncestors(includeSelf)) {
if (search(ancestor))
return ancestor;
}

return undefined;
}

/** Find the first descendant node matching the `search`. */
public findDescendant(search: NodePredicate<Item>, includeSelf = false, type: TraversalType = 'breadth-first') {
for (const descendant of this.traverseDescendants(includeSelf, type)) {
if (search(descendant))
return descendant;
}

return undefined;
}

/** Find the descendant nodes matching the `search`. */
public *findDescendants(search: NodePredicate<Item>, includeSelf = false, type: TraversalType = 'breadth-first') {
for (const descendant of this.traverseDescendants(includeSelf, type)) {
if (search(descendant))
yield descendant;
}
}


/** Does an ancestor node matching the `search` exist? */
public hasAncestor(search: NodePredicate<Item>, includeSelf = false) {
return this.findAncestor(search, includeSelf) !== undefined;
}

/** Does a descendant node matching the `search` exist? */
public hasDescendant(search: NodePredicate<Item>, includeSelf = false, type: TraversalType = 'breadth-first') {
return this.findDescendant(search, includeSelf, type) !== undefined;
}
//#endregion

//#region traversal
Expand Down
42 changes: 42 additions & 0 deletions src/nodes/nodes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type { Identify } from '../utilities/identify.js';
import type { GetChildren, GetParent } from '../utilities/related-items.js';
import type { Relation } from '../utilities/relations.js';
import { HCNode } from './node.js';
import type { NodePredicate } from './node.types.js';
import { nodesToIds, nodesToItems, nodeToId } from './node-conversion.js';

export class Nodes {
Expand Down Expand Up @@ -403,4 +404,45 @@ export class Nodes {
});
}


/** Find the first ancestor node matching the `search`. */
public static findAncestor<Item>(roots: Multiple<HCNode<Item>>, search: NodePredicate<Item>, includeSelf = false) {
for (const root of iterateMultiple(roots)) {
const ancestor = root.findAncestor(search, includeSelf);
if (ancestor)
return ancestor;
}

return undefined;
}

/** Find the first descendant node matching the `search`. */
public static findDescendant<Item>(roots: Multiple<HCNode<Item>>, search: NodePredicate<Item>, includeSelf = false, type: TraversalType = 'breadth-first') {
for (const descendant of this.traverseDescendants(roots, includeSelf, type)) {
if (search(descendant))
return descendant;
}

return undefined;
}

/** Find the descendant nodes matching the `search`. */
public static *findDescendants<Item>(roots: Multiple<HCNode<Item>>, search: NodePredicate<Item>, includeSelf = false, type: TraversalType = 'breadth-first') {
for (const descendant of this.traverseDescendants(roots, includeSelf, type)) {
if (search(descendant))
yield descendant;
}
}


/** Does an ancestor node matching the `search` exist? */
public static hasAncestor<Item>(roots: Multiple<HCNode<Item>>, search: NodePredicate<Item>, includeSelf = false) {
return this.findAncestor(roots, search, includeSelf) !== undefined;
}

/** Does a descendant node matching the `search` exist? */
public static hasDescendant<Item>(roots: Multiple<HCNode<Item>>, search: NodePredicate<Item>, includeSelf = false, type: TraversalType = 'breadth-first') {
return this.findDescendant(roots, search, includeSelf, type) !== undefined;
}

}

0 comments on commit dc426bc

Please sign in to comment.