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;
}
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,
typeOptions?: {+nodeType?: string, +edgeType?: string}
): Edge<EP>[] {
const inEdges = this.getInEdges(nodeAddress, typeOptions);
): {+edge: Edge<EP>, +neighborAddress: Address}[] {
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.
const outEdges = this.getOutEdges(nodeAddress, typeOptions).filter(
(e) => !deepEqual(e.src, e.dst)
);
return [].concat(inEdges, outEdges);
.filter((e) => !deepEqual(e.src, e.dst))
.map((e) => {
return {edge: e, neighborAddress: e.dst};
});
return [].concat(inNeighbors, outNeighbors);
}
/**

View File

@ -251,9 +251,9 @@ describe("graph", () => {
expect(g.getInEdges(demoData.crabNode().address)).toContainEqual(
demoData.crabLoopEdge()
);
const crabAdjacencies = g.getAdjacentEdges(demoData.crabNode().address);
const crabLoops = crabAdjacencies.filter((e) =>
deepEqual(e, demoData.crabLoopEdge())
const crabNeighbors = g.getNeighborhood(demoData.crabNode().address);
const crabLoops = crabNeighbors.filter(({edge, neighborAddress}) =>
deepEqual(edge, demoData.crabLoopEdge())
);
expect(crabLoops).toHaveLength(1);
});
@ -414,9 +414,12 @@ describe("graph", () => {
});
});
});
describe("adjacentEdges", () => {
describe("getNeighborhood", () => {
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 = [
eg.inEdges.a1,
eg.inEdges.a2,
@ -455,46 +458,96 @@ describe("graph", () => {
});
});
});
it("gets adjacent-edges", () => {
const nodeAndExpectedEdgePairs = [
[
demoData.heroNode(),
[
demoData.eatEdge(),
demoData.pickEdge(),
demoData.grabEdge(),
demoData.cookEdge(),
demoData.duplicateCookEdge(),
it("gets neighborhood", () => {
const nodeAndNeighborhood = [
{
node: demoData.heroNode(),
neighborhood: [
{
edge: demoData.eatEdge(),
neighborAddress: demoData.mealNode().address,
},
{
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()],
],
[
demoData.crabNode(),
[
demoData.crabIngredientEdge(),
demoData.grabEdge(),
demoData.crabLoopEdge(),
],
],
[
demoData.mealNode(),
[
demoData.bananasIngredientEdge(),
demoData.crabIngredientEdge(),
demoData.cookEdge(),
demoData.eatEdge(),
demoData.duplicateCookEdge(),
},
{
node: demoData.crabNode(),
neighborhood: [
{
edge: demoData.crabIngredientEdge(),
neighborAddress: demoData.mealNode().address,
},
{
edge: demoData.grabEdge(),
neighborAddress: demoData.heroNode().address,
},
{
edge: demoData.crabLoopEdge(),
neighborAddress: demoData.crabNode().address,
},
],
},
{
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
.advancedMealGraph()
.getAdjacentEdges(node.address);
expectSameSorted(actual, expectedEdges);
.getNeighborhood(node.address);
const sort = (hood) =>
sortBy(hood, (hoodlum) => stringify(hoodlum.edge.address));
expect(sort(actual)).toEqual(sort(neighborhood));
});
});
it("gets out-edges", () => {