Skip to content

Commit

Permalink
feat(arrays): add topoSort()
Browse files Browse the repository at this point in the history
- add topoSort() as lightweight alt for thi.ng/dgraph
- add tests
- update readme
  • Loading branch information
postspectacular committed Oct 31, 2022
1 parent b27fd07 commit f7f2e20
Show file tree
Hide file tree
Showing 6 changed files with 90 additions and 1 deletion.
3 changes: 2 additions & 1 deletion packages/arrays/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ node --experimental-repl-await
> const arrays = await import("@thi.ng/arrays");
```

Package sizes (gzipped, pre-treeshake): ESM: 2.51 KB
Package sizes (gzipped, pre-treeshake): ESM: 2.64 KB

## Dependencies

Expand Down Expand Up @@ -90,6 +90,7 @@ Package sizes (gzipped, pre-treeshake): ESM: 2.51 KB
- [startsWith()](https://github.com/thi-ng/umbrella/tree/develop/packages/arrays/src/starts-with.ts)
- [swap()](https://github.com/thi-ng/umbrella/tree/develop/packages/arrays/src/swap.ts)
- [swizzle()](https://github.com/thi-ng/umbrella/tree/develop/packages/arrays/src/swizzle.ts)
- [topoSort()](https://github.com/thi-ng/umbrella/tree/develop/packages/arrays/src/topo-sort.ts)

### Binary search result predicates

Expand Down
7 changes: 7 additions & 0 deletions packages/arrays/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,14 @@
"aos",
"array",
"binary",
"distance",
"fuzzy",
"levenshtein",
"search",
"shuffle",
"sort",
"swizzle",
"topology",
"typescript"
],
"publishConfig": {
Expand Down Expand Up @@ -141,6 +145,9 @@
},
"./swizzle": {
"default": "./swizzle.js"
},
"./topo-sort": {
"default": "./topo-sort.js"
}
},
"thi.ng": {
Expand Down
1 change: 1 addition & 0 deletions packages/arrays/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@ export * from "./sort-cached.js";
export * from "./starts-with.js";
export * from "./swap.js";
export * from "./swizzle.js";
export * from "./topo-sort.js";
55 changes: 55 additions & 0 deletions packages/arrays/src/topo-sort.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import type { Fn, Nullable, IObjectOf } from "@thi.ng/api";
import { illegalState } from "@thi.ng/errors";

/**
* Takes an object describing a DAG of nodes of type T with keys being node IDs.
* Also takes a function which will be applied to each node and either returns
* an array of node IDs the given node depends on or null if the node has no
* dependencies. Traverses all nodes in the graph (object) and returns an array
* of node IDs in topological dependency order.
*
* @remarks
* An error will be thrown if the graph contains cycles. In this case, the full
* cycle will be part of the error message (see second example below).
*
* @example
* ```ts
* const graph = {
* a: { deps: ["c", "b"] },
* b: {},
* c: { deps: ["d"] },
* d: { deps: ["b"] }
* };
* topoSort(graph, (node) => node.deps);
* // [ "b", "d", "c", "a" ]
*
* // An error will be thrown if the graph contains cycles...
* graph.d.deps.push("a");
*
* topoSort(graph, (node) => node.deps);
* // Uncaught Error: illegal state: dependency cycle: a -> c -> d -> a
* ```
*
* @param nodes
* @param deps
* @returns
*/
export const topoSort = <T>(
nodes: IObjectOf<T>,
deps: Fn<T, Nullable<string[]>>
) => {
const cycles: IObjectOf<boolean> = {};
const topology: string[] = [];
const sort = (id: string, path: string[]) => {
if (cycles[id]) illegalState(`dependency cycle: ${path.join(" -> ")}`);
cycles[id] = true;
const nodeDeps = deps(nodes[id]);
if (nodeDeps) {
for (let d of nodeDeps) sort(d, [...path, d]);
}
cycles[id] = false;
if (!topology.includes(id)) topology.push(id);
};
for (let id in nodes) sort(id, [id]);
return topology;
};
24 changes: 24 additions & 0 deletions packages/arrays/test/toposort.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import * as assert from "assert";
import { group } from "@thi.ng/testament";
import { topoSort } from "../src/index.js";

const graph: Record<string, { deps?: string[] }> = {
a: { deps: ["c", "b"] },
b: {},
c: { deps: ["d"] },
d: { deps: ["b"] },
};

group("topoSort", {
dag: () => {
assert.deepStrictEqual(
topoSort(graph, (x) => x.deps),
["b", "d", "c", "a"]
);
},
"cycle detection": () => {
assert.throws(() =>
topoSort({ ...graph, d: { deps: ["b", "a"] } }, (x) => x.deps)
);
},
});
1 change: 1 addition & 0 deletions packages/arrays/tpl.readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ ${docLink}
- [startsWith()](https://github.com/thi-ng/umbrella/tree/develop/packages/arrays/src/starts-with.ts)
- [swap()](https://github.com/thi-ng/umbrella/tree/develop/packages/arrays/src/swap.ts)
- [swizzle()](https://github.com/thi-ng/umbrella/tree/develop/packages/arrays/src/swizzle.ts)
- [topoSort()](https://github.com/thi-ng/umbrella/tree/develop/packages/arrays/src/topo-sort.ts)

### Binary search result predicates

Expand Down

0 comments on commit f7f2e20

Please sign in to comment.