Replace `Graph.{getAdjacentEdges,getNeighborhood}` (#162)
`Graph.getAdjacentEdges` had a serious defect: for the adjacent edges, it's hard to tell which of the {src,dst} is the neighboring node address and which is the node we called `getAdjacentEdges` on. This commit fixes that limitation by replacing `getAdjacentEdges` with `getNeighborhood`, with a return signature of `{edge: Edge<EP>, neighborAddress: Address}[]` Some yak shaving was required: we needed a version of `expectSameSorted` and, by extension, `sortedByAddress` that takes an accessor to an Addressable, so that we could test that the neighborhoods returned were correct. To satisfy flow, I created `expectSameSortedAccessorized` and `sortedByAddressAccessorized`. Cumbersome, but it worked. ¯\_(ツ)_/¯
This commit is contained in:
parent
28e686c369
commit
678924087a
|
@ -220,16 +220,27 @@ export class Graph<NP, EP> {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
getAdjacentEdges(
|
/**
|
||||||
|
* Find the neighborhood of a given nodeAddress
|
||||||
|
*
|
||||||
|
* By neighborhood, we mean every `{edge, neighborAddress}` such that edge
|
||||||
|
* has `nodeAddress` as either `src` or `dst`, and `neighborAddress` is the
|
||||||
|
* address at the other end of the edge.
|
||||||
|
*/
|
||||||
|
getNeighborhood(
|
||||||
nodeAddress: Address,
|
nodeAddress: Address,
|
||||||
typeOptions?: {+nodeType?: string, +edgeType?: string}
|
typeOptions?: {+nodeType?: string, +edgeType?: string}
|
||||||
): Edge<EP>[] {
|
): {+edge: Edge<EP>, +neighborAddress: Address}[] {
|
||||||
const inEdges = this.getInEdges(nodeAddress, typeOptions);
|
const inNeighbors = this.getInEdges(nodeAddress, typeOptions).map((e) => {
|
||||||
|
return {edge: e, neighborAddress: e.src};
|
||||||
|
});
|
||||||
|
const outNeighbors = this.getOutEdges(nodeAddress, typeOptions)
|
||||||
// If there are self-reference edges, avoid double counting them.
|
// If there are self-reference edges, avoid double counting them.
|
||||||
const outEdges = this.getOutEdges(nodeAddress, typeOptions).filter(
|
.filter((e) => !deepEqual(e.src, e.dst))
|
||||||
(e) => !deepEqual(e.src, e.dst)
|
.map((e) => {
|
||||||
);
|
return {edge: e, neighborAddress: e.dst};
|
||||||
return [].concat(inEdges, outEdges);
|
});
|
||||||
|
return [].concat(inNeighbors, outNeighbors);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -251,9 +251,9 @@ describe("graph", () => {
|
||||||
expect(g.getInEdges(demoData.crabNode().address)).toContainEqual(
|
expect(g.getInEdges(demoData.crabNode().address)).toContainEqual(
|
||||||
demoData.crabLoopEdge()
|
demoData.crabLoopEdge()
|
||||||
);
|
);
|
||||||
const crabAdjacencies = g.getAdjacentEdges(demoData.crabNode().address);
|
const crabNeighbors = g.getNeighborhood(demoData.crabNode().address);
|
||||||
const crabLoops = crabAdjacencies.filter((e) =>
|
const crabLoops = crabNeighbors.filter(({edge, neighborAddress}) =>
|
||||||
deepEqual(e, demoData.crabLoopEdge())
|
deepEqual(edge, demoData.crabLoopEdge())
|
||||||
);
|
);
|
||||||
expect(crabLoops).toHaveLength(1);
|
expect(crabLoops).toHaveLength(1);
|
||||||
});
|
});
|
||||||
|
@ -414,9 +414,12 @@ describe("graph", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe("adjacentEdges", () => {
|
describe("getNeighborhood", () => {
|
||||||
const eg = new ExampleGraph();
|
const eg = new ExampleGraph();
|
||||||
const getEdges = (opts) => eg.graph.getAdjacentEdges(eg.root, opts);
|
const getEdges = (opts) =>
|
||||||
|
eg.graph
|
||||||
|
.getNeighborhood(eg.root, opts)
|
||||||
|
.map(({edge, neighborAddress}) => edge);
|
||||||
const allEdges = [
|
const allEdges = [
|
||||||
eg.inEdges.a1,
|
eg.inEdges.a1,
|
||||||
eg.inEdges.a2,
|
eg.inEdges.a2,
|
||||||
|
@ -455,46 +458,96 @@ describe("graph", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it("gets adjacent-edges", () => {
|
it("gets neighborhood", () => {
|
||||||
const nodeAndExpectedEdgePairs = [
|
const nodeAndNeighborhood = [
|
||||||
[
|
{
|
||||||
demoData.heroNode(),
|
node: demoData.heroNode(),
|
||||||
[
|
neighborhood: [
|
||||||
demoData.eatEdge(),
|
{
|
||||||
demoData.pickEdge(),
|
edge: demoData.eatEdge(),
|
||||||
demoData.grabEdge(),
|
neighborAddress: demoData.mealNode().address,
|
||||||
demoData.cookEdge(),
|
},
|
||||||
demoData.duplicateCookEdge(),
|
{
|
||||||
|
edge: demoData.pickEdge(),
|
||||||
|
neighborAddress: demoData.bananasNode().address,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
edge: demoData.grabEdge(),
|
||||||
|
neighborAddress: demoData.crabNode().address,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
edge: demoData.cookEdge(),
|
||||||
|
neighborAddress: demoData.mealNode().address,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
edge: demoData.duplicateCookEdge(),
|
||||||
|
neighborAddress: demoData.mealNode().address,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
node: demoData.bananasNode(),
|
||||||
|
neighborhood: [
|
||||||
|
{
|
||||||
|
edge: demoData.pickEdge(),
|
||||||
|
neighborAddress: demoData.heroNode().address,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
edge: demoData.bananasIngredientEdge(),
|
||||||
|
neighborAddress: demoData.mealNode().address,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
[
|
},
|
||||||
demoData.bananasNode(),
|
{
|
||||||
[demoData.pickEdge(), demoData.bananasIngredientEdge()],
|
node: demoData.crabNode(),
|
||||||
],
|
neighborhood: [
|
||||||
[
|
{
|
||||||
demoData.crabNode(),
|
edge: demoData.crabIngredientEdge(),
|
||||||
[
|
neighborAddress: demoData.mealNode().address,
|
||||||
demoData.crabIngredientEdge(),
|
},
|
||||||
demoData.grabEdge(),
|
{
|
||||||
demoData.crabLoopEdge(),
|
edge: demoData.grabEdge(),
|
||||||
],
|
neighborAddress: demoData.heroNode().address,
|
||||||
],
|
},
|
||||||
[
|
{
|
||||||
demoData.mealNode(),
|
edge: demoData.crabLoopEdge(),
|
||||||
[
|
neighborAddress: demoData.crabNode().address,
|
||||||
demoData.bananasIngredientEdge(),
|
},
|
||||||
demoData.crabIngredientEdge(),
|
|
||||||
demoData.cookEdge(),
|
|
||||||
demoData.eatEdge(),
|
|
||||||
demoData.duplicateCookEdge(),
|
|
||||||
],
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
node: demoData.mealNode(),
|
||||||
|
neighborhood: [
|
||||||
|
{
|
||||||
|
edge: demoData.bananasIngredientEdge(),
|
||||||
|
neighborAddress: demoData.bananasNode().address,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
edge: demoData.crabIngredientEdge(),
|
||||||
|
neighborAddress: demoData.crabNode().address,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
edge: demoData.cookEdge(),
|
||||||
|
neighborAddress: demoData.heroNode().address,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
edge: demoData.eatEdge(),
|
||||||
|
neighborAddress: demoData.heroNode().address,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
edge: demoData.duplicateCookEdge(),
|
||||||
|
neighborAddress: demoData.heroNode().address,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
|
},
|
||||||
];
|
];
|
||||||
nodeAndExpectedEdgePairs.forEach(([node, expectedEdges]) => {
|
nodeAndNeighborhood.forEach(({node, neighborhood}) => {
|
||||||
const actual = demoData
|
const actual = demoData
|
||||||
.advancedMealGraph()
|
.advancedMealGraph()
|
||||||
.getAdjacentEdges(node.address);
|
.getNeighborhood(node.address);
|
||||||
expectSameSorted(actual, expectedEdges);
|
const sort = (hood) =>
|
||||||
|
sortBy(hood, (hoodlum) => stringify(hoodlum.edge.address));
|
||||||
|
expect(sort(actual)).toEqual(sort(neighborhood));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it("gets out-edges", () => {
|
it("gets out-edges", () => {
|
||||||
|
|
Loading…
Reference in New Issue