Skip to content

Commit 6a498be

Browse files
authored
refactor types (#5)
1 parent e522e0f commit 6a498be

File tree

8 files changed

+82
-100
lines changed

8 files changed

+82
-100
lines changed

src/graph/compound-graph.ts

+1-5
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@ import { upsertSet } from './helpers';
33
export const ROOT_NODE = '_____root_graph_node_____';
44

55
export interface IHierarchy {
6-
addNode(node: string): void;
7-
setParent(node: string, parent: string): void;
6+
setParent(node: string, parent?: string): void;
87
getParent(node: string): string;
98
removeHierarchyNode(node: string): void;
109
getChildren(node?: string): string[];
@@ -15,9 +14,6 @@ export function createHierarchy(): IHierarchy {
1514
const hierarchyParent = new Map<string, string>();
1615

1716
const hierarchy: IHierarchy = {
18-
addNode(node: string) {
19-
hierarchy.setParent(node, ROOT_NODE);
20-
},
2117
setParent(node: string, parent: string = ROOT_NODE) {
2218
// todo make it optimal
2319
for (let [, v] of hierarchyChildren) {

src/graph/dot.ts

+4-12
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,16 @@
1-
import { IUndirectedGraph, meta } from './graph';
1+
import { IUndirectedGraph, GraphReturn } from './graph';
22
import { IHierarchy, ROOT_NODE } from './compound-graph';
33

44
type Writer = ReturnType<typeof makeWriter>;
5-
export function toDot<N, E>(
6-
g: IUndirectedGraph<N, E> & IHierarchy,
7-
args: { intend?: string; compound: true }
8-
): string;
9-
10-
export function toDot<N, E>(
11-
g: IUndirectedGraph<N, E>,
12-
args: { intend?: string; compound?: false }
13-
): string;
5+
type Args = { intend?: string; compound?: boolean; directed?: boolean };
6+
export function toDot<N, E>(g: GraphReturn<N, E>, args: Args): string;
147

158
export function toDot<N, E>(
169
g: any,
17-
{ intend = ' ', compound }: { intend?: string; compound?: boolean }
10+
{ intend = ' ', compound, directed }: Args
1811
) {
1912
const graph: IUndirectedGraph<N, E> & IHierarchy = g;
2013
const writer = makeWriter(intend);
21-
const directed = g[meta]?.directed;
2214
const ec = directed ? '->' : '--';
2315
writer.writeBlock(directed ? 'digraph' : 'graph', () => {
2416
graph.nodes().forEach(([key, value]) => {

src/graph/graph.ts

+65-31
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { Optional } from 'utility-types';
22

33
import { upsertSet, mapUnique } from './helpers';
4+
import { createHierarchy, IHierarchy } from './compound-graph';
45

56
//#region types
6-
export const meta = Symbol();
77
interface IBaseGraphApi<N, E> {
88
/**
99
* get list of all edges
@@ -54,9 +54,6 @@ export interface IUndirectedGraph<N, E> extends IBaseGraphApi<N, E> {
5454
* list of nodes without edges
5555
*/
5656
orphans(node: string): string[];
57-
[meta]?: {
58-
directed: boolean;
59-
};
6057
}
6158
// type for basic graph implementation
6259
export interface IDirectedGraph<N, E> extends IUndirectedGraph<N, E> {
@@ -77,9 +74,6 @@ export interface IDirectedGraph<N, E> extends IUndirectedGraph<N, E> {
7774
* list of outgoing edges for particular node
7875
*/
7976
successors(node: string): string[];
80-
[meta]?: {
81-
directed: boolean;
82-
};
8377
}
8478

8579
type GraphEdge<T> = { to: string; from: string; value?: T };
@@ -88,6 +82,11 @@ type Events = {
8882
onAddNode?: (k: string) => void;
8983
onRemoveNode?: (k: string) => void;
9084
};
85+
86+
type ICompoundGraphApi = Pick<
87+
IHierarchy,
88+
'getChildren' | 'getParent' | 'setParent'
89+
>;
9190
//#endregion
9291

9392
function createBaseGraph<N, E>(events: Events) {
@@ -200,26 +199,41 @@ function createBaseGraph<N, E>(events: Events) {
200199
return graph;
201200
}
202201

202+
export type GraphReturn<N, E> =
203+
| IDirectedGraph<N, E>
204+
| IUndirectedGraph<N, E>
205+
| (IUndirectedGraph<N, E> & ICompoundGraphApi)
206+
| (IUndirectedGraph<N, E> & ICompoundGraphApi);
207+
203208
export function createGraph<N, E>(arg: {
204-
events: Events;
205209
directed: true;
206210
}): IDirectedGraph<N, E>;
207211
export function createGraph<N, E>(arg: {
208-
events: Events;
209212
directed: false;
210213
}): IUndirectedGraph<N, E>;
214+
export function createGraph<N, E>(arg: {
215+
directed?: false;
216+
compound: true;
217+
}): IUndirectedGraph<N, E> & ICompoundGraphApi;
218+
export function createGraph<N, E>(arg: {
219+
directed: true;
220+
compound: true;
221+
}): IUndirectedGraph<N, E> & ICompoundGraphApi;
211222

212223
export function createGraph<N, E>({
213-
events = {},
214224
directed,
225+
compound,
215226
}: {
216227
directed?: boolean;
217-
218-
events?: Events;
219-
}): IDirectedGraph<N, E> {
228+
compound?: boolean;
229+
}): GraphReturn<N, E> {
220230
const inNodes = new Map<string, Set<string>>();
221231
const outNodes = new Map<string, Set<string>>();
222-
const base = createBaseGraph<N, E>(events);
232+
const hierarchy = createHierarchy();
233+
const base = createBaseGraph<N, E>({
234+
onAddNode: hierarchy.setParent,
235+
onRemoveNode: hierarchy.removeHierarchyNode,
236+
});
223237
function reorderEdge<T>(from: string, to: string, value?: T) {
224238
if (!directed && from < to) {
225239
let tmp = from;
@@ -228,7 +242,7 @@ export function createGraph<N, E>({
228242
}
229243
return { from, to, value };
230244
}
231-
const graph: IUndirectedGraph<N, E> = {
245+
const graph = {
232246
getNodeValue: base.getNodeValue,
233247
setNode: base.setNode,
234248
hasNode: base.hasNode,
@@ -287,24 +301,44 @@ export function createGraph<N, E>({
287301
orphans() {
288302
return base.filterNodes(k => !inNodes.has(k) && !outNodes.has(k));
289303
},
304+
predecessors(n: string) {
305+
return Array.from(inNodes.get(n)?.values() || []);
306+
},
307+
successors(n: string) {
308+
return Array.from(outNodes.get(n)?.values() || []);
309+
},
310+
sources() {
311+
return base.filterNodes(k => !inNodes.has(k));
312+
},
313+
sinks() {
314+
return base.filterNodes(k => !outNodes.has(k));
315+
},
290316
};
291-
if (directed) {
292-
Object.assign(graph, {
293-
predecessors(n: string) {
294-
return mapUnique(Array.from(inNodes.get(n)?.values() || []));
295-
},
296-
successors(n: string) {
297-
return mapUnique(Array.from(outNodes.get(n)?.values() || []));
298-
},
299-
sources() {
300-
return base.filterNodes(k => !inNodes.has(k));
301-
},
302-
sinks() {
303-
return base.filterNodes(k => !outNodes.has(k));
304-
},
305-
} as Partial<IDirectedGraph<N, E>>);
317+
const compoundApi = {
318+
getChildren: hierarchy.getChildren,
319+
setParent: (node, parent) => {
320+
if (!base.hasNode(node)) {
321+
base.setNode(node);
322+
}
323+
if (parent && !base.hasNode(parent)) {
324+
base.setNode(parent);
325+
}
326+
return hierarchy.setParent(node, parent);
327+
},
328+
getParent: hierarchy.getParent,
329+
} as ICompoundGraphApi;
330+
331+
if (directed && compound) {
332+
return Object.assign(graph, compoundApi) as IDirectedGraph<N, E> &
333+
ICompoundGraphApi;
334+
} else if (compound) {
335+
return Object.assign(graph, compoundApi) as IUndirectedGraph<N, E> &
336+
ICompoundGraphApi;
337+
} else if (directed) {
338+
return Object.assign(graph) as IDirectedGraph<N, E>;
339+
} else {
340+
return graph as IUndirectedGraph<N, E>;
306341
}
307-
return graph as IDirectedGraph<N, E>;
308342
}
309343

310344
function edgeToString(from: string, to: string) {

src/graph/index.ts

-23
Original file line numberDiff line numberDiff line change
@@ -1,24 +1 @@
1-
import { createHierarchy, IHierarchy } from './compound-graph';
2-
import { IDirectedGraph, IUndirectedGraph, createGraph } from './graph';
3-
export * from './compound-graph';
41
export * from './graph';
5-
6-
export function createCompoundGraph<N, E>(
7-
type: 'graph'
8-
): IHierarchy & IUndirectedGraph<N, E>;
9-
export function createCompoundGraph<N, E>(
10-
type: 'digraph'
11-
): IHierarchy & IDirectedGraph<N, E>;
12-
13-
export function createCompoundGraph<N, E>(type: string): any {
14-
const hierarchy = createHierarchy();
15-
const events = {
16-
onAddNode: hierarchy.addNode,
17-
onRemoveNode: hierarchy.removeHierarchyNode,
18-
};
19-
const graph =
20-
type === 'graph'
21-
? createGraph<N, E>({ events, directed: false })
22-
: createGraph<N, E>({ events, directed: true });
23-
return Object.assign({}, graph, hierarchy);
24-
}

src/graph/tests/Graph.test.ts

-23
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,17 @@
11
import { createGraph, IDirectedGraph } from '../graph';
22

3-
const getEvents = () => ({
4-
onAddNode: jest.fn(),
5-
onRemoveNode: jest.fn(),
6-
});
7-
83
describe('Graph', () => {
9-
let events = getEvents();
104
let digraph: IDirectedGraph<unknown, unknown>;
115

126
beforeEach(() => {
13-
events = getEvents();
147
digraph = createGraph({
15-
events,
168
directed: true,
179
});
1810
});
1911

2012
it('should have a correct initial state', function() {
2113
expect(digraph.nodesCount()).toBe(0);
2214
expect(digraph.edgesCount()).toBe(0);
23-
expect(events.onAddNode.mock.calls.length).toBe(0);
24-
expect(events.onRemoveNode.mock.calls.length).toBe(0);
2515
});
2616

2717
describe('nodes', () => {
@@ -67,29 +57,25 @@ describe('Graph', () => {
6757
expect(digraph.hasNode('a')).toBe(true);
6858
expect(digraph.getNodeValue('a')).toBeUndefined();
6959
expect(digraph.nodesCount()).toBe(1);
70-
expect(events.onAddNode.mock.calls.length).toBe(1);
7160
});
7261

7362
it('should can set a value for the node', () => {
7463
digraph.setNode('a', 'foo');
7564

7665
expect(digraph.getNodeValue('a')).toBe('foo');
77-
expect(events.onAddNode.mock.calls.length).toBe(1);
7866
});
7967

8068
it("should does not change the node's value with a 1-arg invocation", () => {
8169
digraph.setNode('a', 'foo');
8270
digraph.setNode('a');
8371

8472
expect(digraph.getNodeValue('a')).toBe('foo');
85-
expect(events.onAddNode.mock.calls.length).toBe(1);
8673
});
8774

8875
it("should can remove the node's value by passing undefined", () => {
8976
digraph.setNode('a', undefined);
9077

9178
expect(digraph.getNodeValue('a')).toBeUndefined();
92-
expect(events.onAddNode.mock.calls.length).toBe(1);
9379
});
9480

9581
it('should filter idempotent nodes', () => {
@@ -98,7 +84,6 @@ describe('Graph', () => {
9884

9985
expect(digraph.getNodeValue('a')).toBe('foo');
10086
expect(digraph.nodesCount()).toBe(1);
101-
expect(events.onAddNode.mock.calls.length).toBe(1);
10287
});
10388
});
10489

@@ -150,7 +135,6 @@ describe('Graph', () => {
150135

151136
expect(digraph.hasNode('a')).toBe(false);
152137
expect(digraph.nodesCount()).toBe(0);
153-
expect(events.onRemoveNode.mock.calls.length).toBe(0);
154138
});
155139

156140
it('should remove the node if it is in the graph', () => {
@@ -159,7 +143,6 @@ describe('Graph', () => {
159143

160144
expect(digraph.hasNode('a')).toBe(false);
161145
expect(digraph.nodesCount()).toBe(0);
162-
expect(events.onRemoveNode.mock.calls.length).toBe(1);
163146
});
164147

165148
it('should is idempotent', () => {
@@ -169,7 +152,6 @@ describe('Graph', () => {
169152

170153
expect(digraph.hasNode('a')).toBe(false);
171154
expect(digraph.nodesCount()).toBe(0);
172-
expect(events.onRemoveNode.mock.calls.length).toBe(1);
173155
});
174156

175157
it('should remove edges incident on the node', () => {
@@ -178,7 +160,6 @@ describe('Graph', () => {
178160
digraph.removeNode('b');
179161

180162
expect(digraph.edgesCount()).toBe(0);
181-
expect(events.onRemoveNode.mock.calls.length).toBe(1);
182163
});
183164
});
184165

@@ -305,7 +286,6 @@ describe('Graph', () => {
305286

306287
it('should handle undirected graph edges', () => {
307288
const graph = createGraph({
308-
events,
309289
directed: false,
310290
});
311291

@@ -335,7 +315,6 @@ describe('Graph', () => {
335315

336316
it('should return an edge in either direction in an undirected graph', function() {
337317
const graph = createGraph({
338-
events,
339318
directed: false,
340319
});
341320

@@ -374,7 +353,6 @@ describe('Graph', () => {
374353

375354
it('should works with undirected graphs', () => {
376355
const graph = createGraph({
377-
events,
378356
directed: false,
379357
});
380358

@@ -408,7 +386,6 @@ describe('Graph', () => {
408386

409387
it('should works with undirected graphs', () => {
410388
const graph = createGraph({
411-
events,
412389
directed: false,
413390
});
414391

src/graph/tests/__snapshots__/dot.test.ts.snap

+3-3
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,13 @@ exports[`dot converted basic 1`] = `
1313
`;
1414

1515
exports[`dot converted compound 1`] = `
16-
"graph {
16+
"digraph {
1717
\\"1\\"
1818
\\"2\\" [\\"color\\"=\\"red\\"]
1919
\\"3\\"
2020
\\"4\\"
21-
\\"1\\"--\\"2\\"
22-
\\"3\\"--\\"4\\"
21+
\\"1\\"->\\"2\\"
22+
\\"3\\"->\\"4\\"
2323
subgraph \\"3\\" {
2424
\\"4\\";'
2525
}

0 commit comments

Comments
 (0)