Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Covert to TypeScript and make Publishable #4

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file added .npmignore
Empty file.
17 changes: 8 additions & 9 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,32 +1,31 @@
{
"name": "@qiwi/toposort",
"version": "1.0.1",
"version": "1.0.3",
"description": "Topological sort of directed ascyclic graphs (like dependecy lists)",
"main": "src/main/js/index.js",
"main": "dist/src/main/index.js",
"publishConfig": {
"access": "public"
},
"scripts": {
"prepublish": "tsc",
"style": "eslint src",
"style:fix": "yarn style --fix",
"verify": "yarn style && yarn test",
"test": "yarn test:unit",
"test:unit": "c8 -r html -r text -r lcov --exclude src/test uvu src/test/js"
"test:unit": "c8 -r html -r text -r lcov --exclude src/test uvu -r tsm src/test"
},
"files": [
"src/main",
"README.md",
"package.json"
],
"type": "module",
"type": "commonjs",
"types": "dist/src/main/index.d.ts",
"repository": {
"type": "git",
"url": "https://github.com/qiwi-forks/toposort.git"
},
"devDependencies": {
"@types/node": "^20.4.1",
"c8": "^7.12.0",
"eslint": "^8.32.0",
"eslint-config-qiwi": "^2.0.8",
"tsm": "^2.3.0",
"typescript": "^4.9.4",
"uvu": "^0.5.6"
},
Expand Down
3 changes: 1 addition & 2 deletions src/main/js/extra.js → src/main/extra.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
} from './helpers.js'
import { validateEdges, validateArgs, validateDag } from './validators.js'

export function toposortExtra (opts) {
export function toposortExtra (opts: { edges: unknown[][]; throwOnCycle?: boolean; nodes?: unknown[] }) {
validateArgs(opts)

const nodes = opts.nodes || uniqueNodes(opts.edges)
Expand Down Expand Up @@ -36,4 +36,3 @@ export function toposortExtra (opts) {
})
}
}

94 changes: 94 additions & 0 deletions src/main/helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
export function makeOutgoingEdges(arr: unknown[][]) {
return arr.reduce((edges, [from, to]) => {
edges.has(from) || edges.set(from, new Set());
edges.has(to) || edges.set(to, new Set());
edges.get(from).add(to);
return edges;
}, new Map());
}

export function makeIncomingEdges(arr: unknown[][]) {
return arr.reduce((edges, [from, to]) => {
edges.has(from) || edges.set(from, new Set());
edges.has(to) || edges.set(to, new Set());
edges.get(to).add(from);
return edges;
}, new Map());
}

export function getStartNodes(edges: unknown[][]) {
const incomingEdges = makeIncomingEdges(edges);
const startNodes: unknown[] = [];
incomingEdges.forEach(
(value, key) => value.size === 0 && startNodes.push(key)
);

return startNodes;
}

export function makeNodesHash(arr: unknown[]) {
return new Map(arr.map((item, i) => [item, i]));
}

export function uniqueNodes(arr: unknown[][]) {
const res = new Set();
for (let i = 0, len = arr.length; i < len; i++) {
const edge = arr[i];
res.add(edge[0]);
res.add(edge[1]);
}
return [...res];
}

export function visitDepthFirst({
node,
adjacencyMap,
}: {
node: unknown;
adjacencyMap: Map<any, any>;
}) {
const visited = new Set();
const stack = [node];
let cur = node;
while (cur) {
visited.add(cur);
const neighbors = [...adjacencyMap.get(cur)];
stack.push(...neighbors.filter((item) => !visited.has(item)).reverse());
cur = stack.pop();
}
return visited;
}

export function getAdjacencyMapOfIndirectedGraph(edges: unknown[][]) {
return edges.reduce((acc, [from, to]) => {
[
[from, to],
[to, from],
].forEach(([node, neighbor]) => {
const neighbors = acc.get(node);
if (neighbors) {
neighbors.add(neighbor);
} else {
acc.set(node, new Set([neighbor]));
}
});
return acc;
}, new Map());
}

export function groupByComponents({ edges }: { edges: unknown[][] }) {
const adjacencyMap: Map<any, any> = getAdjacencyMapOfIndirectedGraph(edges);
const nodes = uniqueNodes(edges);
const components = [];
const visitedNodes = new Set();
let currentNode = nodes[0];

while (visitedNodes.size < nodes.length) {
const visited = visitDepthFirst({ adjacencyMap, node: currentNode });
components.push(visited);
visited.forEach((node) => visitedNodes.add(node));
currentNode = nodes.find((node) => !visitedNodes.has(node));
}

return components;
}
4 changes: 4 additions & 0 deletions src/main/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from "./extra.js";
export * from "./toposort.js";

export {toposortDefault as default} from "./toposort.js";
86 changes: 0 additions & 86 deletions src/main/js/helpers.js

This file was deleted.

2 changes: 0 additions & 2 deletions src/main/js/index.js

This file was deleted.

43 changes: 0 additions & 43 deletions src/main/js/validators.js

This file was deleted.

26 changes: 14 additions & 12 deletions src/main/js/toposort.js → src/main/toposort.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { makeNodesHash, makeOutgoingEdges, uniqueNodes } from './helpers.js'
import { validateEdges } from './validators.js'

function visitFactory(visited, outgoingEdges, nodesHash, sorted, cursor) {
function visit(node, i, predecessors) {
function visitFactory(visited: Record<string, boolean>, outgoingEdges: Map<unknown, Set<unknown>>, nodesHash: Map<unknown, number>, sorted: any[], cursor: number) {
function visit(node: unknown, i: number, predecessors: Set<unknown>) {
if (predecessors.has(node)) {
let nodeRep
try {
Expand All @@ -20,15 +20,15 @@ function visitFactory(visited, outgoingEdges, nodesHash, sorted, cursor) {
if (visited[i]) return
visited[i] = true

let outgoing = outgoingEdges.get(node) || new Set()
outgoing = [...outgoing]
const outgoingSet: (Set<unknown> | unknown[]) = outgoingEdges.get(node) || new Set()
const outgoing = [...outgoingSet]
i = outgoing.length

if (i) {
predecessors.add(node)
do {
const child = outgoing[--i]
visit(child, nodesHash.get(child), predecessors)
const child: any = outgoing[--i]
visit(child, nodesHash.get(child) as number, predecessors)
} while (i)
predecessors.delete(node)
}
Expand All @@ -39,11 +39,11 @@ function visitFactory(visited, outgoingEdges, nodesHash, sorted, cursor) {
return visit
}

export function toposortCore(nodes, edges) {
let cursor = nodes.length
function toposortCore(nodes: unknown[], edges: unknown[][]) {
const cursor = nodes.length
let i = cursor
const sorted = [cursor]
const visited = {}
const sorted: any[] = []
const visited: Record<string, boolean> = {}
const nodesHash = makeNodesHash(nodes)
const outgoingEdges = makeOutgoingEdges(edges)
const visit = visitFactory(visited, outgoingEdges, nodesHash, sorted, cursor)
Expand All @@ -55,15 +55,17 @@ export function toposortCore(nodes, edges) {
return sorted
}

export function toposort(nodes, edges) {
export function toposort(nodes: unknown[], edges: unknown[][]) {
validateEdges(nodes, edges)

return toposortCore(nodes, edges)
}

export function toposortDefault(edges) {
export function toposortDefault(edges: unknown[][]) {
return toposort(uniqueNodes(edges), edges)
}

toposortDefault.array = toposort

export default toposortDefault

Loading