diff --git a/src/algorithms/__tests__/dag-test.js b/src/algorithms/__tests__/dag-test.js index 3834d0f..d184ee7 100644 --- a/src/algorithms/__tests__/dag-test.js +++ b/src/algorithms/__tests__/dag-test.js @@ -1,6 +1,7 @@ /*globals assert, utils*/ 'use strict'; +import { Set } from '../../_internals/'; import {Graph, DiGraph} from '../../classes'; import JSNetworkXError from '../../exceptions/JSNetworkXError'; import JSNetworkXUnfeasible from '../../exceptions/JSNetworkXUnfeasible'; @@ -162,5 +163,34 @@ export var TestDAG = { G.addCycle([0,1,2]); G.addEdge(3,3); assert(!dag.isAperiodic(G)); + }, + + // dag.ancestors + // https://github.com/networkx/networkx/blob/master/networkx/algorithms/tests/test_dag.py#L281 + testAncestors: function () { + var DG = new DiGraph(); + DG.addEdgesFrom([[1, 2], [1, 3], [4, 2], [4, 3], [4, 5], [2, 6], [5, 6]]); + assert.deepEqual(dag.ancestors(DG, 6), new Set([1, 2, 4, 5])); + assert.deepEqual(dag.ancestors(DG, 3), new Set([1, 4])); + assert.deepEqual(dag.ancestors(DG, 1), new Set()); + }, + testAncestorsInvalidSource: function () { + var DG = new DiGraph(); + DG.addEdgesFrom([[1, 2], [1, 3]]); // tested node is not in graph + assert.throws(() => dag.ancestors(DG, 0), JSNetworkXError); + }, + + // dag.descendants + testDescendants: function () { + var DG = new DiGraph(); + DG.addEdgesFrom([[1, 2], [1, 3], [4, 2], [4, 3], [4, 5], [2, 6], [5, 6]]); + assert.deepEqual(dag.descendants(DG, 1), new Set([2, 3, 6])); + assert.deepEqual(dag.descendants(DG, 4), new Set([2, 3, 5, 6])); + assert.deepEqual(dag.descendants(DG, 3), new Set([])); + }, + testDescendantsInvalidSource: function () { + var DG = new DiGraph(); + DG.addEdgesFrom([[1, 2], [1, 3]]); + assert.throws(() => dag.descendants(DG, 0), JSNetworkXError); } }; diff --git a/src/algorithms/dag.js b/src/algorithms/dag.js index 8de408d..74e5bfb 100644 --- a/src/algorithms/dag.js +++ b/src/algorithms/dag.js @@ -4,6 +4,8 @@ import JSNetworkXError from '../exceptions/JSNetworkXError'; import JSNetworkXUnfeasible from '../exceptions/JSNetworkXUnfeasible'; +import { shortestPathLength } from './shortestPaths'; + import { /*jshint ignore:start*/ Map, @@ -13,8 +15,41 @@ import { gcd } from '../_internals'; -// TODO: descendants -// TODO: ancestors +/** + * Returns all nodes reachable from `source` in `G` + * + * @param {Graph} G A NetworkX directed acyclic graph + * @param {Node} Source: node in `G` + * @return {Set} The descendants of `source` in `G` + */ +export async function descendants(G, source) { + if (!G.hasNode(source)) { + throw new JSNetworkXError( + `The node ${source} is not in the graph` + ) + } + const descendantNodes = new Set(shortestPathLength(G, { source }).keys()); + descendantNodes.delete(source); // cannot be own parent + return descendantNodes; +} + +/** + * Returns all nodes having a path to `source` in `G` + * + * @param {Graph} G A NetworkX directed acyclic graph + * @param {Node} Source: node in `G` + * @return {Set} Set of all nodes that source is descended from + */ +export async function ancestors(G, source) { + if (!G.hasNode(source)) { + throw new JSNetworkXError( + `The node ${source} is not in the graph` + ) + } + const ancestorNodes = new Set(shortestPathLength(G, { target: source }).keys()); + ancestorNodes.delete(source); // cannot be own parent + return ancestorNodes; +} /** * Return `true` if the graph G is a directed acyclic graph (DAG) or