Add core/weightedGraph (#1590)

This module is a simple data type which contains a graph and associated
weights. It provides methods for JSON (de)serialization, constructing
new WeightedGraphs, and for merging them.

Test plan: See included unit tests. The unit tests are simple because
the data type and associated methods are quite simple; the underlying
functions for Graphs and Weights have more extensive testing.

Progress towards #1557.
This commit is contained in:
Dandelion Mané 2020-01-28 16:15:33 -08:00 committed by GitHub
parent 6415da7bc8
commit 50a6a5f6a7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 117 additions and 0 deletions

54
src/core/weightedGraph.js Normal file
View File

@ -0,0 +1,54 @@
// @flow
import {Graph, type GraphJSON} from "./graph";
import {type Weights as WeightsT, type WeightsJSON} from "./weights";
import * as Weights from "./weights";
import {toCompat, fromCompat, type Compatible} from "../util/compat";
/** The WeightedGraph a Graph alongside associated Weights
*
* Any combination of Weights and Graph can make a valid WeightedGraph. If the
* Weights contains weights for node or edge addresses that are not present in
* the graph, then those weights will be ignored. If the graph contains nodes
* or edges which do not correspond to any weights, then default weights will
* be inferred.
*/
export type WeightedGraph = {|+graph: Graph, +weights: WeightsT|};
const COMPAT_INFO = {type: "sourcecred/weightedGraph", version: "0.1.0"};
export type WeightedGraphJSON = Compatible<{|
+graphJSON: GraphJSON,
+weightsJSON: WeightsJSON,
|}>;
/**
* Create a new, empty WeightedGraph.
*/
export function empty(): WeightedGraph {
return {graph: new Graph(), weights: Weights.empty()};
}
export function toJSON(w: WeightedGraph): WeightedGraphJSON {
const graphJSON = w.graph.toJSON();
const weightsJSON = Weights.toJSON(w.weights);
return toCompat(COMPAT_INFO, {graphJSON, weightsJSON});
}
export function fromJSON(j: WeightedGraphJSON): WeightedGraph {
const {graphJSON, weightsJSON} = fromCompat(COMPAT_INFO, j);
const graph = Graph.fromJSON(graphJSON);
const weights = Weights.fromJSON(weightsJSON);
return {graph, weights};
}
/**
* Merge multiple WeightedGraphs together.
*
* This delegates to the semantics of Graph.merge and Weights.merge.
*/
export function merge(ws: $ReadOnlyArray<WeightedGraph>): WeightedGraph {
const graph = Graph.merge(ws.map((w) => w.graph));
const weights = Weights.merge(ws.map((w) => w.weights));
return {graph, weights};
}

View File

@ -0,0 +1,63 @@
// @flow
import * as Weights from "./weights";
import {Graph} from "./graph";
import * as WeightedGraph from "./weightedGraph";
import * as GraphTest from "./graphTestUtil";
describe("core/weightedGraph", () => {
function expectEqual(wg1, wg2) {
expect(wg1.graph.equals(wg2.graph)).toBe(true);
expect(wg1.weights).toEqual(wg2.weights);
}
describe("empty", () => {
it("empty produces an empty WeightedGraph", () => {
const {weights, graph} = WeightedGraph.empty();
expect(graph.equals(new Graph())).toBe(true);
expect(weights).toEqual(Weights.empty());
});
});
describe("toJSON/fromJSON", () => {
it("works for an empty WeightedGraph", () => {
const wg = WeightedGraph.empty();
const wgJSON = WeightedGraph.toJSON(wg);
const wg_ = WeightedGraph.fromJSON(wgJSON);
expectEqual(wg, wg_);
});
it("works for a non-empty WeightedGraph", () => {
const node = GraphTest.node("foo");
const graph = new Graph().addNode(node);
const weights = Weights.empty();
weights.nodeWeights.set(node.address, 5);
const wg = {graph, weights};
const wgJSON = WeightedGraph.toJSON(wg);
const wg_ = WeightedGraph.fromJSON(wgJSON);
expectEqual(wg, wg_);
});
});
describe("merge", () => {
it("merge works on nontrivial graph", () => {
// Not attempting to validate edge case semantics here, since this is just
// a wrapper around Graph.merge and WeightedGraph.merge; those functions
// are tested more thoroughly.
const n1 = GraphTest.node("foo");
const n2 = GraphTest.node("bar");
const g1 = new Graph().addNode(n1);
const g2 = new Graph().addNode(n2);
const w1 = Weights.empty();
w1.nodeWeights.set(n1.address, 1);
const w2 = Weights.empty();
w2.nodeWeights.set(n2.address, 2);
const wg1 = {graph: g1, weights: w1};
const wg2 = {graph: g2, weights: w2};
const g = Graph.merge([g1, g2]);
const w = Weights.merge([w1, w2]);
const wg = WeightedGraph.merge([wg1, wg2]);
const wg_ = {weights: w, graph: g};
expectEqual(wg, wg_);
});
});
});