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:
Dandelion Mané 2018-04-27 15:05:36 -07:00 committed by GitHub
parent 28e686c369
commit 678924087a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 111 additions and 47 deletions

View File

@ -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);
} }
/** /**

View File

@ -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", () => {